﻿
// iTransDlg.cpp : 구현 파일 (앞으로 UTF8로 저장)
//

#include "stdafx.h"
#include "iTrans.h"
#include "iTransDlg.h"
#include "afxdialogex.h"
#include <intrin.h>
#include <algorithm>

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

#define WM_SHUTDOWN			(WM_USER+1)
#define COUNTOF(z)			(sizeof(z)/sizeof(z[0]))
#define DOUBLE_MIN_LIMIT	(0.00001)
#define INT_MILLION			(1000000)

TCHAR *tcRegKey = _T("Software\\BLUEnLIVE\\iTrans");
TCHAR *tcRegNames[] = {
	/*  0 */ _T("smaller than 4GB only"),
	/*  1 */ _T("conversion mode"),
	/*  2 */ _T("conversion profile"),
	/*  3 */ _T("kindness of iDevice"),
	/*  4 */ _T("30fps check"),
	/*  5 */ _T("keep prefix only when needed"),
	/*  6 */ _T("Pixel Aspect Ratio mode"),
	/*  7 */ _T("deinterlace"),
	/*  8 */ _T("keep rotate info"),
	/*  9 */ _T("rotate video"),
	/* 10 */ _T("path first"),
	/* 11 */ _T("copy original file date"),
	/* 12 */ _T("ignore UTC info in MediaInfo"),
	/* 13 */ _T("use FFMpeg threads count"),
	/* 14 */ _T("FFMpeg threads count"),
	/* 15 */ _T("use both audio"),
	/* 16 */ _T("force reencode AAC"),
	/* 17 */ _T("designate audio language"),
	/* 18 */ _T("audio language"),
	/* 19 */ _T("audio bitrate"),
	/* 20 */ _T("normalize audio volume"),
	/* 21 */ _T("normalize audio level in dB"),
	/* 22 */ _T("Do NOT downmix audio"),
	/* 23 */ _T("mux AC3 audio"),
	/* 24 */ _T("extract subtitle to SRT"),
	/* 25 */ _T("subtitle font size"),
	/* 26 */ _T("use both subtitles"),
	/* 27 */ _T("desig ANSI SRT language"),
	/* 28 */ _T("ANSI SRT language"),
	/* 29 */ _T("burn subtitle"),
	/* 30 */ _T("desig default audio language ID"),
	/* 31 */ _T("default audio language ID"),
	/* 32 */ _T("desig default subtitle language ID"),
	/* 33 */ _T("default subtitle language ID"),
	/* 34 */ _T("keep temp files mode"),
	/* 35 */ _T("use external save folder"),
	/* 36 */ _T("temp folder"),
	/* 37 */ _T("save folder")
};
TCHAR *tcLangIDs[] = {
	/* 0 */ _T("ko"),
	/* 1 */ _T("en"),
	/* 2 */ _T("ja"),
	/* 3 */ _T("zh"),
	/* 4 */ _T("de"),
	/* 5 */ _T("fr")
};
TCHAR *tcProgramNameV = _T("iTrans von TEUS.me");
TCHAR *tcRunApp = _T("runapp");
TCHAR *tcShuttingDown = _T("Shutting down...");
TCHAR *tcHibernating = _T("Hibernating...");
TCHAR *tcStopped = _T(" stopped");
TCHAR *tcFailed = _T(" failed");
TCHAR *tcFinished = _T(" finished");
TCHAR *tcLangEq = _T(":lang=");
TCHAR *tcSubLayout = _T(":layout=0x60x0x-1:hdlr=sbtl:group=3:disable");
TCHAR *tcExtErrorK = _T("확장자 오류!");
TCHAR *tcAudioConversionK = _T("오디오 변환");
TCHAR *tcAudioConversion = _T("Audio conversion");
TCHAR *tcAudioDelayStr = _T("**AOLY**");
TCHAR *tcVideoDelayStr = _T("**VOLY**");
TCHAR *tcVideoConversion = _T("Video conversion");
TCHAR *tcFileName = _T("file name");
TCHAR *tc2passLogFilename = _T("itrans2passlog");

unsigned char ucSubConvTable[] = {
	0, 1, 2, 3, 3, 4
};

const int CiTransDlg::ciSTATUSBAR_HEIGHT = 16;
const int CiTransDlg::ciCOLUMN_FILENAME = 0;
const int CiTransDlg::ciCOLUMN_PATHNAME = 1;
const int CiTransDlg::ciCOLUMN_EXTAUDIO = 2;
const int CiTransDlg::ciCOLUMN_EXTSUBTL = 3;
const int CiTransDlg::ciCOLUMN_EXTCOVER = 4;
const int CiTransDlg::ciCOLUMN_EXTCHAPT = 5;
const int CiTransDlg::ciCOLUMN_RESULTMS = 6;


// 응용 프로그램 정보에 사용되는 CAboutDlg 대화 상자입니다.

class CAboutDlg : public CDialog
{
public:
	CString csVersionStr;
	CAboutDlg();

// 대화 상자 데이터입니다.
	enum { IDD = IDD_ABOUTBOX };

	protected:
	virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV 지원입니다.

// 구현입니다.
protected:
	DECLARE_MESSAGE_MAP()
public:
	afx_msg BOOL OnHelpInfo(HELPINFO* pHelpInfo);
	virtual BOOL OnInitDialog();
	afx_msg void OnNMClickSyslinkTEUSme(NMHDR *pNMHDR, LRESULT *pResult);
};

CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
{
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
}

BOOL CAboutDlg::OnHelpInfo(HELPINFO* pHelpInfo)
{
	return TRUE;
	//return CDialog::OnHelpInfo(pHelpInfo);
}


BOOL CAboutDlg::OnInitDialog()
{
	CDialog::OnInitDialog();

	CString csTitle;
	csTitle.Format(_T("%s v%s"), tcProgramNameV, static_cast<LPCTSTR>(csVersionStr));
	GetDlgItem(IDC_STATIC_VERSION)->SetWindowText(csTitle);

	return TRUE;
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
	ON_WM_HELPINFO()
	ON_NOTIFY(NM_CLICK, IDC_SYSLINK1, &CAboutDlg::OnNMClickSyslinkTEUSme)
END_MESSAGE_MAP()

// CiTransDlg 대화 상자


CiTransDlg::CiTransDlg(CWnd* pParent /*=NULL*/)
	: CDialog(CiTransDlg::IDD, pParent)
	, m_iConversionMode(2)
	, m_iIDevice(2)
	, m_bKeepRotate(FALSE)
	, m_iRotate(0)
	, m_iKeepTempFiles(0)
	, m_bCreateLog(FALSE)
	, m_bShutdown(FALSE)
	, m_bMuxAC3(FALSE)
	, m_bBurnSubtitle(FALSE)
	// ex-member-variables
	, o_b4GBonly(FALSE)
	, o_iConversionProfile2(2)
	, o_bCheck30fps(TRUE)
	, o_bKeepPrefixOnlyWhenNeeded(FALSE)
	, o_iPAR(2)
	, o_bDeinterlace(TRUE)
	, o_bPathFirst(TRUE)
	, o_bCopyOriginDate(FALSE)
	, o_bIgnoreUTC(TRUE)
	, o_bFfmpegThreads(FALSE)
	, o_uFfmpegThreads(0)
	, o_bBothAudio(FALSE)
	, o_bForceReEncodeAAC(FALSE)
	, o_bAudioOrder(FALSE)
	, o_iAudioOrder(0)
	, o_iAudioBR(0)
	, o_bNormalizeAudio(FALSE)
	, o_dNormalizeAudioInDB(-0.25)
	, o_bDontDownmixAudio(FALSE)
	, o_bExtract2SRT(FALSE)
	, o_iFontSize(0)
	, o_bBothSubtitles(FALSE)
	, o_bSrtLang(FALSE)
	, o_iSrtLang(0)
	, o_bDefaultLangA(FALSE)
	, o_iDefaultLangA(0)
	, o_bDefaultLangS(FALSE)
	, o_iDefaultLangS(0)
	, o_bUseSaveFolder(FALSE)

{
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CiTransDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	DDX_Check(pDX, IDC_CHECK1, m_bKeepRotate);
	DDX_Check(pDX, IDC_CHECK2, m_bMuxAC3);
	DDX_Check(pDX, IDC_CHECK3, m_bBurnSubtitle);
	DDX_Check(pDX, IDC_CHECK4, m_bCreateLog);
	DDX_Check(pDX, IDC_CHECK5, m_bShutdown);
	DDX_CBIndex(pDX, IDC_COMBO1, m_iIDevice);
	DDX_CBIndex(pDX, IDC_COMBO2, m_iConversionMode);
	DDX_CBIndex(pDX, IDC_COMBO3, m_iRotate);
	DDX_CBIndex(pDX, IDC_COMBO4, m_iKeepTempFiles);
	DDX_Control(pDX, IDC_LIST1, m_clcFiles);
}

BEGIN_MESSAGE_MAP(CiTransDlg, CDialog)
	ON_WM_SYSCOMMAND()
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	ON_WM_HELPINFO()
	ON_BN_CLICKED(IDC_BUTTON1, &CiTransDlg::OnClickedButtonDesig)
	ON_BN_CLICKED(IDC_BUTTON2, &CiTransDlg::OnClickedButtonOption)
	ON_BN_CLICKED(IDC_BUTTON3, &CiTransDlg::OnClickedButtonDelete)
	ON_BN_CLICKED(IDC_BUTTON4, &CiTransDlg::OnClickedButtonDeleteAll)
	ON_BN_CLICKED(IDC_BUTTON5, &CiTransDlg::OnClickedButtonRun)
	ON_BN_CLICKED(IDC_BUTTON6, &CiTransDlg::OnClickedButtonStop)
	ON_BN_CLICKED(IDC_CHECK5, &CiTransDlg::OnClickedCheckShutdown)
	ON_CBN_SELCHANGE(IDC_COMBO1, &CiTransDlg::OnSelchangeComboMachine)
	ON_CBN_SELCHANGE(IDC_COMBO2, &CiTransDlg::OnSelchangeConversionMode)
	ON_CBN_SELCHANGE(IDC_COMBO3, &CiTransDlg::OnSelchangeComboRotate)
	ON_NOTIFY(NM_CLICK, IDC_LIST1, &CiTransDlg::OnClickListFiles)
	ON_NOTIFY(LVN_KEYDOWN, IDC_LIST1, &CiTransDlg::OnKeydownListFiles)
	ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW, 0, 0xFFFF, OnToolTipNotify)
	ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA, 0, 0xFFFF, OnToolTipNotify)
	ON_WM_TIMER()
END_MESSAGE_MAP()


// CiTransDlg 메시지 처리기

BOOL CiTransDlg::OnInitDialog()
{
	CDialog::OnInitDialog();

	// 시스템 메뉴에 "정보..." 메뉴 항목을 추가합니다.

	// IDM_ABOUTBOX는 시스템 명령 범위에 있어야 합니다.
	/*
	ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
	ASSERT(IDM_ABOUTBOX < 0xF000);

	CMenu* pSysMenu = GetSystemMenu(FALSE);
	if (pSysMenu != NULL) {
		BOOL bNameValid;
		CString strAboutMenu;
		bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
		ASSERT(bNameValid);
		if (!strAboutMenu.IsEmpty()) {
			pSysMenu->AppendMenu(MF_SEPARATOR);
			pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
		}
	}
	*/
	// 이 대화 상자의 아이콘을 설정합니다. 응용 프로그램의 주 창이 대화 상자가 아닐 경우에는
	//  프레임워크가 이 작업을 자동으로 수행합니다.
	SetIcon(m_hIcon, TRUE);			// 큰 아이콘을 설정합니다.
	SetIcon(m_hIcon, FALSE);		// 작은 아이콘을 설정합니다.

	m_clcFiles.InitListCtrl();

	EnableToolTips();
	bIsControlEnabled = TRUE;

	redir = NULL;
	csRootPath = GetThisPath();

	const DWORD buff_size = 50;
	LPTSTR lpSystemPath = new TCHAR[buff_size];
	const DWORD dwPathLen = GetEnvironmentVariable(_T("PATH"), lpSystemPath, buff_size);
	if (dwPathLen == 0) {
		/* fine, some failure or no HOME */
	} else if (dwPathLen>buff_size) {
		if (lpSystemPath) delete []lpSystemPath;
		lpSystemPath = new TCHAR[dwPathLen];

		const DWORD dwNewSize = GetEnvironmentVariable(_T("PATH"), lpSystemPath, dwPathLen);

		if (dwNewSize && dwNewSize<=dwPathLen) {
			csSystemPath = lpSystemPath;
			delete []lpSystemPath;
		}
	} else {
		csSystemPath = lpSystemPath;
		delete []lpSystemPath;
	}

	csTempPath = _T("");
	csSavePath = _T("");

	{
		LoadFromRegistry();
		_tmkdir(csTempPath);

		if (!CSmallUtilz4iTrans::CheckTempFolder(csTempPath, m_iKeepTempFiles==1)) {
			MessageBox(_T("임시 폴더 사용 불가!"));
		}

		EnableControls(TRUE);

		csTempPath.TrimRight(_TCHAR('\\'));

	}
	if (csTempPath.GetLength())
		csTempPath += _TCHAR('\\');

	if (!CreateStatusBar()) {
		MessageBox(_T("Status Bar 생성 실패!"));
	}

	m_clcFiles.SetButtonRemoveAll((CButton*)GetDlgItem(IDC_BUTTON4));

	InitFont();
	static unsigned char fontsizestable[] = {18, 24, 36, 48};
	fontsizes = fontsizestable;

	bOnConversion = false;
	bPersianBlueBox = FALSE;

	CSmallUtilz4iTrans::getAppVersionNumber(appVersionStr);

	CString csTitle;
	csTitle.Format(_T("%s v%s"), tcProgramNameV, static_cast<LPCTSTR>(appVersionStr));
	// (build xxx)를 없애기로 결정
	{
		int pos = csTitle.Find(_TCHAR('('));
		if (pos>=0) csTitle = csTitle.Left(pos);
	}
	SetWindowText(csTitle);

	DrawBoxRotate();

	RegisterWindowMessage(_T("TaskbarButtonCreated"));

	getOsMemInfos();
	if (bIsWindowsVistaOrGreater) {
		GetDlgItem(IDC_CHECK5)->SetWindowText(_T("최대 절전"));
	}

	return TRUE;  // 포커스를 컨트롤에 설정하지 않으면 TRUE를 반환합니다.
}

void CiTransDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
	if (((nID & 0xFFF0) == IDM_ABOUTBOX)) {
		modalAbout();
	} else {
		CDialog::OnSysCommand(nID, lParam);
	}
}

// 대화 상자에 최소화 단추를 추가할 경우 아이콘을 그리려면
//  아래 코드가 필요합니다. 문서/뷰 모델을 사용하는 MFC 응용 프로그램의 경우에는
//  프레임워크에서 이 작업을 자동으로 수행합니다.

void CiTransDlg::OnPaint()
{
	if (IsIconic()) {
		CPaintDC dc(this); // 그리기를 위한 디바이스 컨텍스트입니다.

		SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

		// 클라이언트 사각형에서 아이콘을 가운데에 맞춥니다.
		int cxIcon = GetSystemMetrics(SM_CXICON);
		int cyIcon = GetSystemMetrics(SM_CYICON);
		CRect rect;
		GetClientRect(&rect);
		int x = (rect.Width() - cxIcon + 1) / 2;
		int y = (rect.Height() - cyIcon + 1) / 2;

		// 아이콘을 그립니다.
		dc.DrawIcon(x, y, m_hIcon);
	} else {
		if (bPersianBlueBox || m_bShutdown) {
			CPaintDC dc(this);

			CPen pen1;
			CBrush brush1;

			if (bPersianBlueBox) {
				// Persian Blue
				pen1.CreatePen(PS_SOLID, 1, 0x00BB391C);

				// "transparent" brush
				brush1.CreateStockObject(HOLLOW_BRUSH);

				// select brush and pen
				dc.SelectObject(&pen1);
				dc.SelectObject(&brush1);
				// rectangle with magenta frame and "transparent" background

				CRect rect;
				GetDlgItem(IDC_COMBO3)->GetWindowRect(&rect);
				ScreenToClient(&rect);
				dc.Rectangle(rect.left-2, rect.top-2, rect.right+2, rect.bottom+2);
				dc.Rectangle(rect.left-3, rect.top-3, rect.right+3, rect.bottom+3);
			}

			CPen pen2;
			CBrush brush2;

			if (m_bShutdown) {
				// Persian Red
				pen2.CreatePen(PS_SOLID, 1, 0x003333CC);

				// "transparent" brush
				brush2.CreateStockObject(HOLLOW_BRUSH);

				// select brush and pen
				dc.SelectObject(&pen2);
				dc.SelectObject(&brush2);
				// rectangle with magenta frame and "transparent" background

				CRect rect;
				GetDlgItem(IDC_CHECK5)->GetWindowRect(&rect);
				ScreenToClient(&rect);
				dc.Rectangle(rect.left-3, rect.top-2, rect.right+1, rect.bottom+2);
				dc.Rectangle(rect.left-4, rect.top-3, rect.right+2, rect.bottom+3);
			}

		}

		CDialog::OnPaint();
	}
}

// 사용자가 최소화된 창을 끄는 동안에 커서가 표시되도록 시스템에서
//  이 함수를 호출합니다.
HCURSOR CiTransDlg::OnQueryDragIcon()
{
	return static_cast<HCURSOR>(m_hIcon);
}



BOOL CiTransDlg::PreTranslateMessage(MSG* pMsg)
{
	if ((pMsg->message == WM_KEYDOWN) && (pMsg->wParam == VK_ESCAPE))
		return TRUE;

	return CDialog::PreTranslateMessage(pMsg);
}


BOOL CiTransDlg::OnHelpInfo(HELPINFO* pHelpInfo)
{
	modalAbout();

	return TRUE;
	//return CDialog::OnHelpInfo(pHelpInfo);
}


void CiTransDlg::OnSelchangeConversionMode()
{
	UpdateData();
	CSmallUtilz4iTrans::EnableDialgItem(m_hWnd, IDC_COMBO1, m_iConversionMode);

	static int IDCs[] = {	IDC_CHECK1,	IDC_COMBO3,	IDC_CHECK3	};
	for (int i=0; i<COUNTOF(IDCs); i++) {
		CSmallUtilz4iTrans::EnableDialgItem(m_hWnd, IDCs[i], m_iConversionMode==3);
	}

	DrawBoxRotate();
}

void CiTransDlg::OnSelchangeComboMachine()
{
	UpdateData();

	CSmallUtilz4iTrans::EnableDialgItem(m_hWnd, IDC_COMBO2, TRUE);

	static int IDCs[] = {	IDC_CHECK1,	IDC_COMBO3, IDC_CHECK3	};
	for (int i=0; i<COUNTOF(IDCs); i++) {
		CSmallUtilz4iTrans::EnableDialgItem(m_hWnd, IDCs[i], m_iConversionMode==3);
	}

	DrawBoxRotate();
}

void CiTransDlg::OnSelchangeComboRotate()
{
	UpdateData();
	DrawBoxRotate();
}



void CiTransDlg::OnClickedCheckShutdown()
{
	UpdateData();
	SetTBProgressState(bOnConversion?(m_bShutdown?TBPF_ERROR:TBPF_NORMAL):TBPF_NOPROGRESS);
	DrawBoxShutdown();
}


void CiTransDlg::OnClickListFiles(NMHDR *pNMHDR, LRESULT *pResult)
{
	//LPNMITEMACTIVATE pNMItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
	if (bOnConversion) return;
	CSmallUtilz4iTrans::EnableDialgItem(m_hWnd, IDC_BUTTON3, m_clcFiles.GetSelectedCount());

	*pResult = 0;
}


void CiTransDlg::OnKeydownListFiles(NMHDR *pNMHDR, LRESULT *pResult)
{
	if (bOnConversion) return;
	CSmallUtilz4iTrans::EnableDialgItem(m_hWnd, IDC_BUTTON3, m_clcFiles.GetSelectedCount());

	LPNMLVKEYDOWN pLVKeyDow = reinterpret_cast<LPNMLVKEYDOWN>(pNMHDR);
	if (pLVKeyDow->wVKey == VK_DELETE)
		OnClickedButtonDelete();

	*pResult = 0;
}

void CiTransDlg::OnClickedButtonDesig()
{
	static TCHAR szFilters[]=
		_T("Video files|*.avi;*.divx;*.mkv;*.flv;*.ogm;*.ogg;*.mp4;*.mov;*.qt;*.3gp;*.swf;*.wmv;*.asf;*.mpg;*.mpeg;*.dat;*.vob;*.webm;*.rm;*.rmvb;*.ra|All Files (*.*)|*.*||");

	CFileDialog fileDlg (TRUE, _T("변환할 비디오 지정"), NULL,
		OFN_ALLOWMULTISELECT | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY, szFilters, this);

	const DWORD numberOfFiles = 100;
	const DWORD fileNameMaxLen = MAX_PATH+1;
	const DWORD bufferSize = (numberOfFiles * fileNameMaxLen)+1;
	TCHAR *fileNamesBuffer = new TCHAR[bufferSize];

	ZeroMemory(fileNamesBuffer, sizeof(TCHAR)*bufferSize);

	fileDlg.m_ofn.lpstrFile = fileNamesBuffer;
	fileDlg.m_ofn.nMaxFile = bufferSize;

	CString fileNameArray[numberOfFiles];

	int iCtr=0;
	if( fileDlg.DoModal()==IDOK ) {
		POSITION pos = fileDlg.GetStartPosition();
		while (pos) {
			fileNameArray[iCtr] = fileDlg.GetNextPathName(pos);
			iCtr++;
		}
	}

	delete []fileNamesBuffer;

	int iNoneASCII = 0;

	for (int i=0; i<iCtr; i++) {
		if (m_clcFiles.FindFilename(fileNameArray[i])<0) {
			if (m_clcFiles.bIsASCII(fileNameArray[i]))
				m_clcFiles.AddTailFilename(fileNameArray[i]);
			else
				iNoneASCII++;
		}
	}

	if (iNoneASCII) {
		CString cs;
		cs.Format(_T("%d개의 파일에서 파일명에 비 ASCII 문자 포함!"), iNoneASCII);
		MessageBox(cs);
	}
};

void CiTransDlg::OnClickedButtonOption()
{
	COptionDlg optDlg;
	initOptDlg(optDlg);

	if (optDlg.DoModal() == IDOK) {
		optDlg.SaveToRegistry(false);
		copyFromOptionDialog(optDlg);
	}
	UpdateData(FALSE);
	EnableControls(TRUE);
};


void CiTransDlg::OnClickedButtonDelete()
{
	m_clcFiles.RemoveSelected();
	static int IDCs[] = {	IDC_BUTTON3,	IDC_BUTTON4	};
	for (int i=0; i<2; i++) {
		CSmallUtilz4iTrans::EnableDialgItem(
			m_hWnd,
			IDCs[i],
			i?m_clcFiles.GetItemCount():m_clcFiles.GetSelectedCount()
			);
	}
};


void CiTransDlg::OnClickedButtonDeleteAll()
{
	m_clcFiles.DeleteAllItems();
	m_clcFiles.removeAllCSAFilename();
	CSmallUtilz4iTrans::EnableDialgItem(m_hWnd, IDC_BUTTON3, FALSE);
	CSmallUtilz4iTrans::EnableDialgItem(m_hWnd, IDC_BUTTON4, FALSE);
};


void CiTransDlg::OnClickedButtonStop()
{
	if (redir) redir->Stop();
	l_bThreadStopped = TRUE;
};

void CiTransDlg::OnClickedButtonRun()
{
	// FFMpeg용 threads 옵션부터 지정
	// -threads 3
	// 01234567890123

	//TCHAR ffmpegThreadsTstr[14];
	UINT threads = (o_bFfmpegThreads)?o_uFfmpegThreads:(si.dwNumberOfProcessors-1);
	_stprintf_s(ffmpegThreadsTstr, 14, _T("-threads %u "), threads);

	bOnConversion = true;
	UpdateData();

	if (!checkFolders()) return;

	csaTempFiles.RemoveAll();
	csTempPath83 = csGetShortName(csTempPath);

	MakeLogFilename();

	CreateLog();
	l_csToolsRoot = csRootPath+_T("tools\\");


	// 체크가 켜져있어도 disabled 되어있으면 언체크랑 마찬가지라 별도 변수로 분리
	l_bBurnSubtitle = (m_iConversionMode==3) && m_bBurnSubtitle;
	if (!checkExternalApps()) {
		bOnConversion = false;
		return;
	}

	WriteLog(_T("File check"), _T(" completed"));

	/*
	CString csNewSystemPath(csMP4Box);
	int iPosRev = csNewSystemPath.ReverseFind(_TCHAR('\\'));
	csNewSystemPath = csNewSystemPath.Left(iPosRev) + _TCHAR(';') + csSystemPath;

	SetEnvironmentVariable(_T("PATH"), csNewSystemPath);
	WriteLog(_T("new path setting: `")+csNewSystemPath.Left(iPosRev)+_T(";%PATH%`"));
	*/

	//실행하면 일단 옵션 저장부터 시작
	COptionDlg optDlg;
	initOptDlg(optDlg);
	optDlg.SaveToRegistry(false);
	WriteOptionsToLog();

	l_iCount = m_clcFiles.GetItemCount();
	l_iSuccessCount = 0;
	l_iExtAudioDelay = 0;

	if (!l_iCount) return;

	if (bPersianBlueBox) {
		if (MessageBox(_T("영상 회전이 지정되었습니다. 변환할까요?"), NULL, MB_YESNO) != IDYES)
			return;
	}

	EnableControls(FALSE);

	for (int i=0; i<l_iCount; i++) {
		m_clcFiles.SetItemText(i, ciCOLUMN_RESULTMS, _T("대기중"));
	}

	l_dwTimeTotal = GetTickCount();
	l_bThreadStopped = FALSE;

	// 루프 종료시 삭제될 예정 → 그런 거 없도록 변경완료

	SetTBProgressState(m_bShutdown?TBPF_ERROR:TBPF_NORMAL);

	for (l_i=0; l_i<l_iCount; l_i++) {


//////////////////////
// step1. 작업준비
//////////////////////
		WriteLog(_T("# job log"), NULL, false);
		WriteLog(_T("## step"), _T("1. preparing job"), false);

		//SetTBProgressValue(l_i*STEP6_WEIGHT_SUM+STEP1_WEIGHT_SUM, l_iCount*STEP6_WEIGHT_SUM);
		drawActiveProgress(1, 0);
		if (!step1_prepareJob()) continue;

//////////////////////
// step2. 자막처리
//////////////////////
WriteLog(_T("## step"), _T("2. processing subtitle"), false);

		//SetTBProgressValue(l_i*STEP6_WEIGHT_SUM+STEP2_WEIGHT_SUM, l_iCount*STEP6_WEIGHT_SUM);
		drawActiveProgress(2, 0);
		if (!step2_processSubtitle()) {
			EnableControls(TRUE);
			goto finishjob;
		}

//////////////////////
// step3. 오디오 변환
//////////////////////
WriteLog(_T("## step"), _T("3. processing audio"), false);

		//SetTBProgressValue(l_i*STEP6_WEIGHT_SUM+STEP3_WEIGHT_SUM, l_iCount*STEP6_WEIGHT_SUM);
		drawActiveProgress(3, 0);
		if (!step3_convertAudio()) {
			EnableControls(TRUE);
			goto finishjob;
		}

//////////////////////
// step4. 챕터 변환
//////////////////////
WriteLog(_T("## step"), _T("4. processing chapter"), false);

		//SetTBProgressValue(l_i*STEP6_WEIGHT_SUM+STEP4_WEIGHT_SUM, l_iCount*STEP6_WEIGHT_SUM);
		drawActiveProgress(4, 0);
		if (!step4_convertChapter()) {
			EnableControls(TRUE);
			goto finishjob;
		}

//////////////////////
// step5. 비디오 변환
//////////////////////
WriteLog(_T("## step"), _T("5. processing video"), false);

		//SetTBProgressValue(l_i*STEP6_WEIGHT_SUM+STEP5_WEIGHT_SUM, l_iCount*STEP6_WEIGHT_SUM);
		drawActiveProgress(5, 0);

		switch (step5_convertVideo()) {
		case 1:	continue;
		case 2:	EnableControls(TRUE);	goto finishjob;
		}


//////////////////////
// step6. 비디오 합침
//////////////////////
WriteLog(_T("## step"), _T("6. muxing video"), false);
		
		//SetTBProgressValue(l_i*STEP6_WEIGHT_SUM+STEP6_WEIGHT_SUM, l_iCount*STEP6_WEIGHT_SUM);
		drawActiveProgress(6, 0);

		switch (step6_muxVideo()) {
		case 1:	continue;
		case 2:	EnableControls(TRUE);	goto finishjob;
		}
	}

finishjob:

	RemoveTempFiles();

	l_dwTimeTotal = GetTickCount()-l_dwTimeTotal;

	CString csTime;

	if (l_bThreadStopped) {
		SetTBProgressState(TBPF_ERROR);

		l_dwTime = GetTickCount() - l_dwTime;

		csTime = _T("중단: ");
		csTime += tcGetElapsedTime(l_dwTime);
		m_clcFiles.SetItemText(l_i, ciCOLUMN_RESULTMS, csTime);

		csTime = _T("작업 중단! / ");
		csTime += tcGetElapsedTime(l_dwTimeTotal);
		
		WriteLog(_T("# job"), _T(" stopped"), false);
	} else {
		csTime = _T("작업 종료! / ");
		csTime += tcGetElapsedTime(l_dwTimeTotal);
		
		WriteLog(_T("# job"), tcFinished, false);
	}

	UpdateData();
	if (m_bShutdown && !l_bThreadStopped) {
		WriteLog(bIsWindowsVistaOrGreater?tcHibernating:tcShuttingDown);

		SetTimer(WM_SHUTDOWN, 1000, NULL);
	} else {
		if (l_iSuccessCount) {
			if (!l_bThreadStopped) {
				CString csCount;
				csCount.Format(_T("fin/%d"), l_iCount);
				SetStatusBarText(0, const_cast<LPTSTR>(static_cast<LPCTSTR>(csCount)));
			}

			CFinishDlg fin;
			fin.m_csMsg = csTime;
			SetTBProgressState(TBPF_INDETERMINATE);
			if (fin.DoModal() == IDOK) {
				for (int i=l_iCount-1; i>=0; i--) {
					if (m_clcFiles.GetItemText(i, ciCOLUMN_RESULTMS).Left(2).Compare(_T("완료")) == 0) {
						m_clcFiles.DeleteItem(i);
						m_clcFiles.removeAtCSAFilename(i);
					}
				}
			}
		} else {
			SetTBProgressState(TBPF_INDETERMINATE);
			MessageBox(csTime);
		}

		// 확인사살
		RemoveTempFiles();

		EnableControls(TRUE);
		SetStatusBarText(0, _T(""));
		SetStatusBarText(1, _T(""));
		bOnConversion = false;
	}

	SetTBProgressState(TBPF_NOPROGRESS);
};

BOOL CiTransDlg::CreateStatusBar()
{
	int nTotWidth;
	CRect rect;
	this->GetWindowRect(&rect);
	rect.top = rect.bottom - ciSTATUSBAR_HEIGHT;
	BOOL bRvStatOK = m_StatusBar.Create(WS_CHILD | WS_BORDER | WS_VISIBLE,
		rect, this, IDC_STATUSBAR);
	if (bRvStatOK == NULL) {
		return false;
	}

	CRect rWin;
	this->GetWindowRect(&rWin);

	nTotWidth = rWin.right - rWin.left;

	int m_Width[4];

	m_Width[0] = 40;
	m_Width[1] = 550;
	m_Width[2] = nTotWidth;
	m_Width[3] = -1;

	m_StatusBar.SetMinHeight(ciSTATUSBAR_HEIGHT);
	m_StatusBar.SetParts(4, m_Width);

	return true;
}

void CiTransDlg::SetStatusBarText(int iPos, TCHAR *tc)
{
	m_StatusBar.SetText(tc, iPos, 0);
}


void CiTransDlg::initOptDlg(COptionDlg &optionDlg)
{
	optionDlg.lpRegKey = tcRegKey;

	for (int i=0; i<(sizeof(tcRegNames)/sizeof(tcRegNames[0])); i++) {
		optionDlg.lpRegNames[i] = tcRegNames[i];
	}

	UpdateData(TRUE);
	copyToOptionDialog(optionDlg);
}

BOOL CiTransDlg::OnToolTipNotify( UINT id, NMHDR * pNMHDR, LRESULT * pResult )
{
	// Get the tooltip structure.
	TOOLTIPTEXT *pTTT = (TOOLTIPTEXT *)pNMHDR;

	// Check once again that the idFrom holds handle itself.
	if (pTTT->uFlags & TTF_IDISHWND) {
		// Get the control's ID.
		UINT nID = ::GetDlgCtrlID((HWND)pNMHDR->idFrom);


		if (!nID || (!bIsControlEnabled && (nID != IDC_CHECK5))) return FALSE;

		// Now you have the ID. depends on control,
		// set your tooltip message.

		if (bIsWindowsVistaOrGreater && (nID == IDC_CHECK5)) {
			pTTT->lpszText = _T("변환 완료 후 최대 절전 모드로 전환");

			return TRUE;
		} else {
			static UINT nIDs[] = {
				IDC_COMBO1,
				IDC_COMBO2,
				IDC_COMBO3,
				IDC_CHECK1,
				IDC_CHECK2,
				IDC_CHECK3,
				IDC_CHECK4,
				IDC_CHECK5
			};

			static LPTSTR lpToolTips[] = {
				_T("업로드 대상 기기"),
				_T("비디오 변환 방식을 지정.\r\n재인코딩은 H.264로만 처리함"),
				_T("영상 회전 지정"),
				_T("영상 회전시 소스 파일의 회전정보 반영"),
				_T("원본 오디오에 AC3가 있는 경우 생성되는 파일에 AC3 오디오를 포함"),
				_T("영상에 자막을 직접 입힘.\r\n비디오 강제 재인코딩시에만 동작하며, 자막을 별도로 저장하진 않음"),
				_T("바탕화면에 변환 로그 파일(iTransLog_날짜_시간.md) 생성"),
				_T("변환 완료 후 시스템 종료")
			};

			::SendMessage(pNMHDR->hwndFrom, TTM_SETMAXTIPWIDTH, 0, 800);
			for (int i=0; i<COUNTOF(nIDs); i++) {
				if (nIDs[i]==nID) {
					pTTT->lpszText = lpToolTips[i];

					return TRUE;
				}
			}
		}
	}

	// Not handled.
	return FALSE;
}

void CiTransDlg::EnableControls(BOOL bEnabled)
{
	static BOOL bButtonStatus = FALSE;
	if (bIsControlEnabled && !bEnabled) {
		bButtonStatus = GetDlgItem(IDC_BUTTON1)->IsWindowEnabled();
	}
	bIsControlEnabled = bEnabled;

	m_clcFiles.DragAcceptFiles(bEnabled);

	static UINT nIDs0[] = {
		IDCANCEL,
		IDC_BUTTON1,	IDC_BUTTON2,	IDC_BUTTON5,
		IDC_COMBO4,		IDC_CHECK2,		IDC_CHECK4,
		IDC_COMBO2
	};
	
	for (int i=0; i<(sizeof(nIDs0)/sizeof(nIDs0[0])); i++) {
		CSmallUtilz4iTrans::EnableDialgItem(m_hWnd, nIDs0[i], bEnabled);
	}

	CSmallUtilz4iTrans::EnableDialgItem(m_hWnd, IDC_BUTTON6, !bEnabled);
	CSmallUtilz4iTrans::EnableDialgItem(m_hWnd, IDC_BUTTON3, bEnabled && bButtonStatus);
	CSmallUtilz4iTrans::EnableDialgItem(m_hWnd, IDC_BUTTON4, bEnabled && m_clcFiles.GetItemCount());

	GetDlgItem(IDC_BUTTON5)->ShowWindow(bEnabled?SW_SHOW:SW_HIDE);
	GetDlgItem(IDC_BUTTON6)->ShowWindow(bEnabled?SW_HIDE:SW_SHOW);
	UpdateData();

	static int IDCs[] = {	IDC_CHECK1,	IDC_COMBO3, IDC_CHECK3	};
	for (int i=0; i<COUNTOF(IDCs); i++) {
		CSmallUtilz4iTrans::EnableDialgItem(m_hWnd, IDCs[i], bEnabled && (m_iConversionMode==3));
	}

	CSmallUtilz4iTrans::EnableDialgItem(m_hWnd, IDC_COMBO1, bEnabled && m_iConversionMode);

	DrawBoxRotate();
}

void CiTransDlg::DrawBoxRotate()
{
	bPersianBlueBox = ((m_iConversionMode==3) && (m_iRotate>0));

	CRect rect;
	GetDlgItem(IDC_COMBO3)->GetWindowRect(&rect);
	ScreenToClient(&rect);
	rect.left-=3;
	rect.top-=3;
	rect.right+=3;
	rect.bottom+=3;

	InvalidateRect(&rect, TRUE);
}

void CiTransDlg::DrawBoxShutdown()
{
	CRect rect;
	GetDlgItem(IDC_CHECK5)->GetWindowRect(&rect);
	ScreenToClient(&rect);
	rect.left-=4;
	rect.top-=3;
	rect.right+=2;
	rect.bottom+=3;

	InvalidateRect(&rect, TRUE);
}

void CiTransDlg::SetTBProgressValue(ULONGLONG progress, ULONGLONG max)
{
	if (SUCCEEDED(CoInitialize(NULL))) {
		CComPtr<ITaskbarList3> ptbl3;
		ptbl3.CoCreateInstance(CLSID_TaskbarList);
		if(ptbl3!=NULL) {
			ptbl3->SetProgressState(m_hWnd, TBPF_NORMAL);
			ptbl3->SetProgressValue(m_hWnd, progress, max);
		}
		CoUninitialize();
	}
}

void CiTransDlg::SetTBProgressState(TBPFLAG tbpFlags) {
	if (SUCCEEDED(CoInitialize(NULL))) {
		CComPtr<ITaskbarList3> ptbl3;
		ptbl3.CoCreateInstance(CLSID_TaskbarList);
		if(ptbl3!=NULL) {
			ptbl3->SetProgressState(m_hWnd, tbpFlags);
		}
		CoUninitialize();
	}
}

void CiTransDlg::copyToOptionDialog(COptionDlg &optionDlg)
{
	optionDlg.m_b4GBonly = this->o_b4GBonly;
	optionDlg.m_iConversionMode = this->m_iConversionMode;
	optionDlg.m_iConversionProfile = this->o_iConversionProfile2;
	optionDlg.m_iIDevice = this->m_iIDevice;
	optionDlg.m_bCheck30fps = this->o_bCheck30fps;
	optionDlg.m_bKeepPrefixOnlyWhenNeeded = this->o_bKeepPrefixOnlyWhenNeeded;
	optionDlg.m_iPAR = this->o_iPAR;
	optionDlg.m_bDeinterlace = this->o_bDeinterlace;
	optionDlg.m_bKeepRotate = this->m_bKeepRotate;
	optionDlg.m_iRotate = this->m_iRotate;
	optionDlg.m_bPathFirst = this->o_bPathFirst;
	optionDlg.m_bCopyOriginDate = this->o_bCopyOriginDate;
	optionDlg.m_bIgnoreUTC = this->o_bIgnoreUTC;
	optionDlg.m_bFfmpegThreads = this->o_bFfmpegThreads;
	optionDlg.m_uFfmpegThreads = this->o_uFfmpegThreads;
	optionDlg.m_bBothAudio = this->o_bBothAudio;
	optionDlg.m_bForceReEncodeAAC = this->o_bForceReEncodeAAC;
	optionDlg.m_bAudioOrder = this->o_bAudioOrder;
	optionDlg.m_iAudioOrder = this->o_iAudioOrder;
	optionDlg.m_iAudioBR = this->o_iAudioBR;
	optionDlg.m_bNormalizeAudio = this->o_bNormalizeAudio;
	optionDlg.m_dNormalizeAudioInDB = this->o_dNormalizeAudioInDB;
	optionDlg.m_bDontDownmixAudio = this->o_bDontDownmixAudio;
	optionDlg.m_bMuxAC3 = this->m_bMuxAC3;
	optionDlg.m_bExtract2SRT = this->o_bExtract2SRT;
	optionDlg.m_iFontSize = this->o_iFontSize;
	optionDlg.m_bBothSubtitles = this->o_bBothSubtitles;
	optionDlg.m_bSrtLang = this->o_bSrtLang;
	optionDlg.m_iSrtLang = this->o_iSrtLang;
	optionDlg.m_bBurnSubtitle = this->m_bBurnSubtitle;
	optionDlg.m_bDefaultLangA = this->o_bDefaultLangA;
	optionDlg.m_iDefaultLangA = this->o_iDefaultLangA;
	optionDlg.m_bDefaultLangS = this->o_bDefaultLangS;
	optionDlg.m_iDefaultLangS = this->o_iDefaultLangS;
	optionDlg.m_iKeepTempFiles = this->m_iKeepTempFiles;
	optionDlg.m_bUseSaveFolder = this->o_bUseSaveFolder;

	optionDlg.csSavePath = this->csSavePath;
	optionDlg.csTempPath = this->csTempPath;

}

void CiTransDlg::copyFromOptionDialog(COptionDlg &optionDlg)
{
	this->o_b4GBonly = optionDlg.m_b4GBonly;
	this->m_iConversionMode = optionDlg.m_iConversionMode;
	this->o_iConversionProfile2 = optionDlg.m_iConversionProfile;
	this->m_iIDevice = optionDlg.m_iIDevice;
	this->o_bCheck30fps = optionDlg.m_bCheck30fps;
	this->o_bKeepPrefixOnlyWhenNeeded = optionDlg.m_bKeepPrefixOnlyWhenNeeded;
	this->o_iPAR = optionDlg.m_iPAR;
	this->o_bDeinterlace = optionDlg.m_bDeinterlace;
	this->m_bKeepRotate = optionDlg.m_bKeepRotate;
	this->m_iRotate = optionDlg.m_iRotate;
	this->o_bPathFirst = optionDlg.m_bPathFirst;
	this->o_bCopyOriginDate = optionDlg.m_bCopyOriginDate;
	this->o_bIgnoreUTC = optionDlg.m_bIgnoreUTC;
	this->o_bFfmpegThreads = optionDlg.m_bFfmpegThreads;
	this->o_uFfmpegThreads = optionDlg.m_uFfmpegThreads;
	this->o_bBothAudio = optionDlg.m_bBothAudio;
	this->o_bForceReEncodeAAC = optionDlg.m_bForceReEncodeAAC;
	this->o_bAudioOrder = optionDlg.m_bAudioOrder;
	this->o_iAudioOrder = optionDlg.m_iAudioOrder;
	this->o_iAudioBR = optionDlg.m_iAudioBR;
	this->o_bNormalizeAudio = optionDlg.m_bNormalizeAudio;
	this->o_dNormalizeAudioInDB = optionDlg.m_dNormalizeAudioInDB;
	this->o_bDontDownmixAudio = optionDlg.m_bDontDownmixAudio;
	this->m_bMuxAC3 = optionDlg.m_bMuxAC3;
	this->o_bExtract2SRT = optionDlg.m_bExtract2SRT;
	this->o_iFontSize = optionDlg.m_iFontSize;
	this->o_bBothSubtitles = optionDlg.m_bBothSubtitles;
	this->o_bSrtLang = optionDlg.m_bSrtLang;
	this->o_iSrtLang = optionDlg.m_iSrtLang;
	this->m_bBurnSubtitle = optionDlg.m_bBurnSubtitle;
	this->o_bDefaultLangA = optionDlg.m_bDefaultLangA;
	this->o_iDefaultLangA = optionDlg.m_iDefaultLangA;
	this->o_bDefaultLangS = optionDlg.m_bDefaultLangS;
	this->o_iDefaultLangS = optionDlg.m_iDefaultLangS;
	this->m_iKeepTempFiles = optionDlg.m_iKeepTempFiles;
	this->o_bUseSaveFolder = optionDlg.m_bUseSaveFolder;

	this->csSavePath = optionDlg.csSavePath;
	this->csTempPath = optionDlg.csTempPath;
}

LPCTSTR CiTransDlg::GetThisPath()
{
	static TCHAR szModuleName[MAX_PATH*4];
	static BOOL bIsFirst = TRUE;
	if (bIsFirst) {
		::GetModuleFileName(NULL,szModuleName,MAX_PATH*4);
		*(_tcsrchr( szModuleName, _TCHAR('\\') ) + 1) = NULL;
		bIsFirst = FALSE;
	}

	return szModuleName;
}

void CiTransDlg::LoadFromRegistry()
{
	HKEY hkey;

	if (RegOpenKey(HKEY_CURRENT_USER, tcRegKey, &hkey) == ERROR_SUCCESS) {
		static void *optionValues[36];

		//파일 및 비디오 관련 옵션
		optionValues[0] = &o_b4GBonly;
		optionValues[1] = &m_iConversionMode;

		// 레지스트리에는
		// 0:Slow .. 4:Ultra fast, 5:HiBR+Very slow, 6:Default 로 저장하지만
		// 드롭다운의 값은
		// 0:Default, 1:Slow .. 5:Ultra fast, 6:HiBR+Very slow
		optionValues[2] = &o_iConversionProfile2;

		optionValues[3] = &m_iIDevice;
		optionValues[4] = &o_bCheck30fps;
		optionValues[5] = &o_bKeepPrefixOnlyWhenNeeded;
		optionValues[6] = &o_iPAR;
		optionValues[7] = &o_bDeinterlace;
		optionValues[8] = &m_bKeepRotate;
		optionValues[9] = &m_iRotate;
		optionValues[10] = &o_bPathFirst;
		optionValues[11] = &o_bCopyOriginDate;
		optionValues[12] = &o_bIgnoreUTC;
		optionValues[13] = &o_bFfmpegThreads;
		optionValues[14] = &o_uFfmpegThreads;

		//오디오 옵션
		optionValues[15] = &o_bBothAudio;
		optionValues[16] = &o_bForceReEncodeAAC;
		optionValues[17] = &o_bAudioOrder;
		optionValues[18] = &o_iAudioOrder;
		optionValues[19] = &o_iAudioBR;
		optionValues[20] = &o_bNormalizeAudio;
		DWORD dwNormalizeAudioInDB;
		optionValues[21] = &dwNormalizeAudioInDB;
		optionValues[22] = &o_bDontDownmixAudio;
		optionValues[23] = &m_bMuxAC3;

		//자막 옵션
		optionValues[24] = &o_bExtract2SRT;
		optionValues[25] = &o_iFontSize;
		optionValues[26] = &o_bBothSubtitles;
		optionValues[27] = &o_bSrtLang;
		optionValues[28] = &o_iSrtLang;
		optionValues[29] = &m_bBurnSubtitle;

		//기본언어 설정 관련 옵션
		optionValues[30] = &o_bDefaultLangA;
		optionValues[31] = &o_iDefaultLangA;
		optionValues[32] = &o_bDefaultLangS;
		optionValues[33] = &o_iDefaultLangS;

		//작업 폴더 관련 옵션
		optionValues[34] = &m_iKeepTempFiles;
		optionValues[35] = &o_bUseSaveFolder;

		for (int i=0; i<36; i++) {
			if (getRegValBoolOrInt(hkey, tcRegNames[i], optionValues[i]) &&
				(i==20)) {
					o_dNormalizeAudioInDB = CSmallUtilz4iTrans::DWORD2decibel(dwNormalizeAudioInDB);
			}
		}

		//작업 폴더 관련 옵션
		getRegValCString(hkey, tcRegNames[36], csTempPath);
		getRegValCString(hkey, tcRegNames[37], csSavePath);

		RegCloseKey(hkey);

		UpdateData(FALSE);

		DrawBoxRotate();
	}
}

void CiTransDlg::InitFont()
{
	HFONT hFont;
	LOGFONT logfont;
	SetUIFont(hFont, logfont, _T("Terminal"), 7);
	SendDlgItemMessage(IDC_STATUSBAR, WM_SETFONT, (WPARAM)hFont, (LPARAM)TRUE);
}

void CiTransDlg::SetUIFont(HFONT &hFont, LOGFONT &lFont, TCHAR *tcFont, long lSize/*, BOOL bBold*/)
{
	CClientDC dc(this);

	LOGFONT logfont;

	logfont.lfHeight = -MulDiv(lSize, GetDeviceCaps(dc.m_hDC, LOGPIXELSY), 72);
	logfont.lfWidth = 0;
	logfont.lfEscapement = 0;
	logfont.lfOrientation = 0;
	logfont.lfWeight = FW_THIN;
	logfont.lfItalic = FALSE;
	logfont.lfUnderline = FALSE;
	logfont.lfStrikeOut = 0;
	logfont.lfCharSet = OEM_CHARSET;
	logfont.lfOutPrecision = OUT_DEFAULT_PRECIS;
	logfont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
	logfont.lfQuality = DEFAULT_QUALITY;
	logfont.lfPitchAndFamily = DEFAULT_PITCH | FF_DONTCARE;
	_stprintf_s(logfont.lfFaceName, 32, tcFont);

	lFont = logfont;

	hFont = CreateFontIndirect(&logfont);
}

TCHAR* CiTransDlg::GetRegValueTCHAR(HKEY hkey, TCHAR *tcName)
{
	static TCHAR result[MAX_PATH];
	DWORD dwType;
	DWORD dwLen = MAX_PATH;
	if (RegQueryValueEx(hkey, tcName, NULL, &dwType, reinterpret_cast<LPBYTE>(result), &dwLen) != ERROR_SUCCESS)
		return NULL;

	if (dwType != REG_SZ)
		return NULL;

	return result;
}

BOOL CiTransDlg::bGetRegValueDWORD(HKEY hkey, TCHAR *tcName, DWORD &dwRet)
{
	DWORD result;
	DWORD dwType;
	DWORD dwLen = sizeof(DWORD);
	if (RegQueryValueEx(hkey, tcName, NULL, &dwType, reinterpret_cast<LPBYTE>(&result), &dwLen) != ERROR_SUCCESS)
		return FALSE;

	if (dwType != REG_DWORD)
		return FALSE;

	dwRet = result;
	return TRUE;
}

bool CiTransDlg::getRegValBoolOrInt(HKEY hKey, TCHAR *name, void *var)
{
	DWORD dw;
	if (bGetRegValueDWORD(hKey, name, dw)) {
		*(reinterpret_cast<int*>(var)) = static_cast<int>(dw);
		return true;
	}
	return false;
}

bool CiTransDlg::getRegValCString(HKEY hKey, TCHAR *name, CString &var)
{
	TCHAR *tc;
	tc = GetRegValueTCHAR(hKey, name);
	if (tc) {
		var = tc;
		return true;
	}
	return false;
}

void CiTransDlg::OnTimer(UINT_PTR nIDEvent)
{
	if (nIDEvent == WM_SHUTDOWN) {
		static int sec = 0;
		SetTBProgressState(TBPF_ERROR);
		SetTBProgressValue(sec+1, 20);

		CString cs;
		cs.Format(_T("%s %d"), bIsWindowsVistaOrGreater?tcHibernating:tcShuttingDown, 19-sec);
		SetStatusBarText(2, const_cast<LPTSTR>(static_cast<LPCTSTR>(cs)));

		sec++;

		if (sec>=20) {
			SetStatusBarText(2, bIsWindowsVistaOrGreater?tcHibernating:tcShuttingDown);
			EnableControls(TRUE);

			SetStatusBarText(1, _T(""));
			bOnConversion = false;

			KillTimer(WM_SHUTDOWN);

			CStringArray csaAppNamesLong;
			csaAppNamesLong.Add(csShutdownLong);
			RunApp(csShutdown, bIsWindowsVistaOrGreater?_T("/h /f"):_T("/s /t 5"), csaAppNamesLong);

			// 확인사살
			RemoveTempFiles();
			SetTBProgressState(TBPF_NOPROGRESS);
		}
	}

	CDialog::OnTimer(nIDEvent);
}

void CiTransDlg::RunApp(LPCTSTR lpApp, LPCTSTR lpParam, CStringArray &csaAppsLong, bool writeLog, double *dMaxVolume)
{
	if (redir) return;
	CString csApp(lpApp);
	if (csApp.Find(_TCHAR('\\'))<0) return;
	if (!csaAppsLong.GetSize()) return;

	while (*lpParam==_TCHAR(' ')) lpParam++;

	if (writeLog) {
		csLogMsg.Format(_T("`%s %s`"), lpApp, lpParam);
		WriteLog(csLogMsg);
	}

	CString csBatch;
	CSmallUtilz4iTrans::makeBatchFileName(csBatch, csTempPath83, tcRunApp);
	csaTempFiles.Add(csBatch);

	FILE *f;
	if (!_tfopen_s(&f, csBatch, _T("wt")) && f) {
		CString cs;
		if (csApp.Find(_TCHAR(' '))>=0) csApp = _TCHAR('\"')+csApp+_TCHAR('\"');
		cs.Format(_T("%s %s\n"), static_cast<LPCTSTR>(csApp), lpParam);

		printfuni2ansi(f, static_cast<LPCTSTR>(cs));

		fclose(f);
	}

	// 프로세스 제거용 lpAppNameLong
	CStringArray csaAppNames;
	for (int i=0; i<csaAppsLong.GetSize(); i++) {
		LPCTSTR lpAppLong = csaAppsLong.GetAt(i);
		LPCTSTR lpAppNameLong = CSmallUtilz4iTrans::getAppNameOnly(lpAppLong);
		csaAppNames.Add(lpAppNameLong);
	}

	// 실행
	redir = new CRedirect(csBatch, csaAppNames, reinterpret_cast<CStatusBarCtrl*>(GetDlgItem(IDC_STATUSBAR)), NULL, _T("processing..."));
	redir->Run(dMaxVolume);

	// Batch file 삭제
	if (m_iKeepTempFiles<2)
		CSmallUtilz4iTrans::DeleteSomeFile(csBatch, m_iKeepTempFiles==1);

	SetStatusBarText(2, _T(""));
	delete redir;
	redir = NULL;
}

void CiTransDlg::RemoveTempFiles()
{
	if (m_iKeepTempFiles==2) return;

	for (int i=0; i<csaTempFiles.GetSize(); i++) {
		CSmallUtilz4iTrans::DeleteSomeFile(csaTempFiles.GetAt(i), m_iKeepTempFiles==1);
	}
}

void CiTransDlg::MakeLogFilename()
{
	static TCHAR strPath[ MAX_PATH ];

	SHGetSpecialFolderPath(
		0,       // Hwnd
		strPath, // String buffer.
		CSIDL_DESKTOPDIRECTORY, // CSLID of folder
		FALSE ); // Create if doesn't exists?
	CString csRootPath(strPath);
	csRootPath.TrimRight(_TCHAR('\\'));
	csRootPath += _T("\\iTransLog_");

	SYSTEMTIME stime;
	GetLocalTime(&stime);

	csRootPath.AppendFormat(_T("%04d%02d%02d_%02d%02d%02d.md"), stime.wYear, stime.wMonth, stime.wDay, stime.wHour, stime.wMinute, stime.wSecond);

	_tcsncpy_s(strPath, MAX_PATH, csRootPath, MAX_PATH);

	lpLogFilename = strPath;

}

void CiTransDlg::CreateLog()
{
	if (!m_bCreateLog) return;
	clog.CreateLog(lpLogFilename);
	//clog.WriteSystemInfo(osVersionstr, appVersionStr);
	clog.WriteSystemInfo(sOsInfoStr, sMemInfoStr, appVersionStr);
}

void CiTransDlg::WriteLog(LPCTSTR lpStr, LPCTSTR lpStr2, bool bWriteTime)
{
	if (!m_bCreateLog) return;
	clog.WriteLog(lpStr, lpStr2, bWriteTime);
}

void CiTransDlg::WriteOptionsToLog()
{
	DWORD dw[36];
	dw[0] = DWORD(o_b4GBonly);
	dw[1] = DWORD(m_iConversionMode);
	dw[2] = DWORD(o_iConversionProfile2);
	dw[3] = DWORD(m_iIDevice);
	dw[4] = DWORD(o_bCheck30fps);
	dw[5] = DWORD(o_bKeepPrefixOnlyWhenNeeded);
	dw[6] = DWORD(o_iPAR);
	dw[7] = DWORD(o_bDeinterlace);
	dw[8] = DWORD(m_bKeepRotate);
	dw[9] = DWORD(m_iRotate);
	dw[10] = DWORD(o_bPathFirst);
	dw[11] = DWORD(o_bCopyOriginDate);
	dw[12] = DWORD(o_bIgnoreUTC);
	dw[13] = DWORD(o_bFfmpegThreads);
	dw[14] = DWORD(o_uFfmpegThreads);
	dw[15] = DWORD(o_bBothAudio);
	dw[16] = DWORD(o_bForceReEncodeAAC);
	dw[17] = DWORD(o_bAudioOrder);
	dw[18] = DWORD(o_iAudioOrder);
	dw[19] = DWORD(o_iAudioBR);
	dw[20] = DWORD(o_bNormalizeAudio);
	dw[21] = CSmallUtilz4iTrans::decibel2DWORD(o_dNormalizeAudioInDB);
	dw[22] = DWORD(o_bDontDownmixAudio);
	dw[23] = DWORD(m_bMuxAC3);
	dw[24] = DWORD(o_bExtract2SRT);
	dw[25] = DWORD(o_iFontSize);
	dw[26] = DWORD(o_bBothSubtitles);
	dw[27] = DWORD(o_bSrtLang);
	dw[28] = DWORD(o_iSrtLang);
	dw[29] = DWORD(m_bBurnSubtitle);
	dw[30] = DWORD(o_bDefaultLangA);
	dw[31] = DWORD(o_iDefaultLangA);
	dw[32] = DWORD(o_bDefaultLangS);
	dw[33] = DWORD(o_iDefaultLangS);
	dw[34] = DWORD(m_iKeepTempFiles);
	dw[35] = DWORD(o_bUseSaveFolder);

	WriteLog(_T("# options"), NULL, false);
	WriteLog(_T("## file and video options"), NULL, false);
	CString csLog;

	for (int i=0; i<15; i++) {
		csLog.Format(_T("*%s = %d*"), tcRegNames[i], dw[i]);
		WriteLog(csLog);
	}

	WriteLog(_T("## audio options"), NULL, false);

	for (int i=15; i<24; i++) {
		csLog.Format(_T("*%s = %d*"), tcRegNames[i], dw[i]);
		WriteLog(csLog);
	}

	WriteLog(_T("## subtitle options"), NULL, false);

	for (int i=24; i<30; i++) {
		csLog.Format(_T("*%s = %d*"), tcRegNames[i], dw[i]);
		WriteLog(csLog);
	}


	WriteLog(_T("## default language ids options"), NULL, false);

	for (int i=30; i<34; i++) {
		csLog.Format(_T("*%s = %d*"), tcRegNames[i], dw[i]);
		WriteLog(csLog);
	}

	WriteLog(_T("## folder options"), NULL, false);
	csLog.Format(_T("*%s = %d*"), tcRegNames[34], dw[34]);
	WriteLog(csLog);
	if (csTempPath.IsEmpty()) {
		WriteLog(_T("*Temp Folder = (none)*"));
	} else {
		csLog.Format(_T("*%s = %s*"), tcRegNames[36], static_cast<LPCTSTR>(csTempPath));
		WriteLog(csLog);
	}
	csLog.Format(_T("*%s = %d*"), tcRegNames[35], dw[35]);
	WriteLog(csLog);
	if (csSavePath.IsEmpty()) {
		WriteLog(_T("*Save Folder = (none)*"));
	} else {
		csLog.Format(_T("*%s = %s*"), tcRegNames[37], static_cast<LPCTSTR>(csSavePath));
		WriteLog(csLog);
	}
}

void CiTransDlg::WriteHDDSizeToLog(LPCTSTR lpTarget)
{
	unsigned long long llFreeBytesToCaller,
		llTotalBytes,
		llFreeBytes;

	BOOL fResult;
	TCHAR tcDrive[3] = {0};
	tcDrive[0] = lpTarget[0];
	tcDrive[1] = lpTarget[1];

	fResult = GetDiskFreeSpaceEx(tcDrive,
		(PULARGE_INTEGER)&llFreeBytesToCaller,
		(PULARGE_INTEGER)&llTotalBytes,
		(PULARGE_INTEGER)&llFreeBytes);

	if (!fResult)
		return;

	int iFree = int(llFreeBytes/1024/1024);
	CString csT1;
	csT1.Format(_T("%d"), iFree);
	CString csT2;

	while (csT1.GetLength()>3) {
		csT2 = _TCHAR(',')+csT1.Right(3);
		csT1 = csT1.Left(csT1.GetLength()-3);
	}
	csT2 = csT1+csT2;

	CString csMsg;
	csMsg.Format(_T(" - %s drive free space: %sMB"), tcDrive, static_cast<LPCTSTR>(csT2));

	clog.WriteLog(csMsg);
}

CString CiTransDlg::csGetShortName(LPCTSTR lpFilename)
{
	long     length = 0;
	TCHAR*   buffer = NULL;

	length = GetShortPathName(lpFilename, NULL, 0);
	if (length == 0) return _T("");

	buffer = new TCHAR[length];

	length = GetShortPathName(lpFilename, buffer, length);
	if (length == 0) return _T("");

	CString csRet(buffer);
	delete [] buffer;

	return csRet;
}

void CiTransDlg::printfuni2ansi(FILE *f, const TCHAR *tc)
{
	CT2A c(tc);
	fprintf(f, "%s", c);
}

CString CiTransDlg::csCompositeKorean(LPCTSTR lpFilename)
{
	static CComposeKorU composek;

	WCHAR *wcRet = composek.CompositeUniStr((LPTSTR)lpFilename, TRUE);
	CString csRet(wcRet);
	delete wcRet;

	return csRet;
}

void CiTransDlg::AddTempList(CStringArray &csaTemp, CString cs)
{
	// "식별자"파일명 포맷인 경우 처리
	int iPos = cs.Find(_TCHAR('\"'));
	if (iPos>=0) {
		cs.Delete(0, iPos+1);
		iPos = cs.Find(_TCHAR('\"'));
		if (iPos>=0) cs.Delete(0, iPos+1);
	}

	cs.TrimLeft();
	if (cs.IsEmpty()) return;

	for (int i=0; i<csaTemp.GetSize(); i++) {
		if (csaTemp.GetAt(i).Compare(cs) == 0) return;
	}

	csaTemp.Add(cs);
}

BOOL CiTransDlg::bGetMaxResolution(int iMachine, double dFPS)
{
	switch (iMachine) {
	case 1:
		// iPhone4는 level 3.1의 해상도까지 지원함
		//  720×480@80.0 (13)
		//  720×576@66.7 (11)
		// 1280×720@30.0 (5)
		// () 안은 참조 프레임 수인데, 실제로는 15 이하로 통일

		if (dFPS <= 30.0) {
			l_iWidthMax = 1280;
			l_iHeightMax = 720;
		} else if (dFPS <= 66.7) {
			l_iWidthMax = 720;
			l_iHeightMax = 576;
		} else {
			l_iWidthMax = 720;
			l_iHeightMax = 480;

			return (dFPS <= 80.0);
		}
		return TRUE;

	case 2:
		// iPhone4s는 level 4.1의 해상도까지 지원함
		// 1,280×720@68.3 (9)
		// 1,920×1,080@30.1 (4)
		// 2,048×1,024@30.0 (4)
		// () 안은 참조 프레임 수인데, 실제로는 15 이하로 통일

		// 뭔가 이상한 조건은 고려 대상에서 제외
		/*
		if (dFPS <= 30.0) {
			l_iWidthMax = 2048;
			l_iHeightMax = 1024;
		} else */
		if (dFPS <= 30.1) {
			l_iWidthMax = 1920;
			l_iHeightMax = 1080;
		} else if (dFPS <= 68.3) {
			l_iWidthMax = 1280;
			l_iHeightMax = 720;
		} else {
			return FALSE;
		}
		return TRUE;

	default:
		return FALSE;
	}
}

BOOL CiTransDlg::bConvertResolution(int iWidth, int iHeight)
{
	// 1. 최대 해상도보다 크면 비율에 맞게 줄임
	// 2. 단, 원래 해생도가 홀수이며 bypass인 경우는 짝수로 변환하지 않음
	// 3. H>W 이면 H와 W를 바꿔서 지정함 (2012.2.23, 해상도 규칙은 H와 W가 바뀌어도 동작함)

	int iWidthMax = l_iWidthMax;
	int iHeightMax = l_iHeightMax;
	if (iHeight > iWidth) std::swap(iWidthMax, iHeightMax);

	l_iWidthNew = iWidth;
	l_iHeightNew = iHeight;
	if ((iWidth <= iWidthMax) && (iHeight <= iHeightMax)) return FALSE;

	double dWHRatio = double(iWidth)/iHeight;
	double dWHRatioMax = double(iWidthMax)/iHeightMax;

	if (dWHRatioMax < dWHRatio) {
		// 기준(Max)에 비해 영상 종횡비의 가로가 더 큰 경우
		l_iWidthNew = iWidthMax;
		l_iHeightNew = int(l_iWidthNew/dWHRatio + 0.5);
	} else {
		// 기준(Max)에 비해 영상 종횡비의 세로가 더 크거나 같은 경우
		l_iHeightNew = iHeightMax;
		l_iWidthNew = int(l_iHeightNew*dWHRatio + 0.5);
	}

	return TRUE;
}

CString CiTransDlg::csGetLanguageID(LPCTSTR lp)
{
	// ko delay=100ms 등에서 ko 추출
	// 연속된 2개 또는 3개의 영문자 추출함

	CString ret;
	if (!lp) return ret;

	while (*lp) {
		if ((*lp == _TCHAR(' ')) || (*lp == _TCHAR('.'))) {
			if (ret.GetLength()==2 || ret.GetLength()==3) return ret;
			ret = _T("");
			lp++;
		} else if (_istalpha(*lp)) {
			ret += (*lp);
			lp++;
		} else {
			ret = _T("");
			while ((*lp) && (*lp != _TCHAR(' '))) {
				lp++;
			}
		}
	}

	if (ret.GetLength()!=2 && ret.GetLength()!=3) ret = _T("");
	return ret;
}

void CiTransDlg::checkTempFile(CString &csTempFile)
{
	if (!CSmallUtilz4iTrans::IsFileorfolder(csTempFile) && !CSmallUtilz4iTrans::IsFileorfolder(csTempFile, true)) {
		return;
	}

	_tremove(csTempFile);

	if (CSmallUtilz4iTrans::IsFileorfolder(csTempFile) || CSmallUtilz4iTrans::IsFileorfolder(csTempFile, true)) {
		// 새 파일명 만들기
		int dot = csTempFile.ReverseFind(_TCHAR('\\'));
		CString path = csTempFile.Left(dot+1);
		CString filename(csTempFile);
		filename.Delete(0, dot+1);

		CString csLog = _T("temp ") + (tcFileName + (_T(" changed: \"")+csTempFile))+_T("\" → \"");

		while (true) {
			int num = rand() % 1000;
			csTempFile.Format(_T("%s%03d %s"), static_cast<LPCTSTR>(path), num, static_cast<LPCTSTR>(filename));

			if (!CSmallUtilz4iTrans::IsFileorfolder(csTempFile) && !CSmallUtilz4iTrans::IsFileorfolder(csTempFile, true)) {
				csLog += (csTempFile.Right(filename.GetLength()+4)+_TCHAR('\"'));
				WriteLog(csLog);
				return;
			}
		}
	}
}

void CiTransDlg::append_l_csFileInfo_AudSubChinfo()
{
	LPCTSTR lpInfoAdded = _T("");

	if (l_mediainfo.iAudioCount>0) {
		l_mediainfo.getAudioType(l_mediainfo.afAudioFormat[0], lpInfoAdded);

		l_csFileInfo.AppendFormat(_T(", %s"), lpInfoAdded);
		if (l_mediainfo.iAudioCount>1) l_csFileInfo.AppendFormat(_T("/%d"), l_mediainfo.iAudioCount);
	}

	if (l_bSubInt) {
		if (l_mediainfo.ffFileFormat == FFMT_MKV) {
			switch (l_mediainfo.sfSubFormat[0]) {
			case SFMT_SRT:	lpInfoAdded = _T(", SRT");	break;
			case SFMT_ASS:	lpInfoAdded = _T(", ASS");	break;
			default:		lpInfoAdded = _T(", ?");
			}
		} else {
			lpInfoAdded = (l_mediainfo.sfSubFormat[0] == SFMT_TTXT)?_T(", TTXT"):_T(", ?");
		}

		l_csFileInfo+=lpInfoAdded;

		if (l_mediainfo.iSubCountSbtlOnly>1)
			l_csFileInfo.AppendFormat(_T("/%d"), l_mediainfo.iSubCountSbtlOnly);
	}

	if (l_mediainfo.csaChapters.GetSize()) {
		lpInfoAdded = _T(", CH");
	} else if (l_mediainfo.iSubChapterID >= 0) {
		lpInfoAdded = _T(", CHtx");
	}
}

bool CiTransDlg::isAudioExtractable(CMediaInfo & mediainfo, int iAudioPos)
{
	switch (mediainfo.afAudioFormat[iAudioPos]) {
	case AFMT_AAC:
		return ((!o_bForceReEncodeAAC) && ((mediainfo.iChannels[iAudioPos]<=2) || (o_bDontDownmixAudio)));
		break;

	case AFMT_AC3:
		return (!!m_bMuxAC3);
		break;

	default:
		return false;
	}

	return false;
}

// 0: 스레드 중단, 1: 추출 실패, 2: 추출 완료
// 오디오 추출은 AAC/AC3가 특정 조건일 때만 실시
// (AAC: 재인코딩 안할 때, AC3: AC3 삽입할 때)
// 컨테이너 종류는 확인하지 않고, AAC/AC3가 아니면 1을 리턴
// 다른 포맷은 호출하기 전에 알아서 차단해야 함
int CiTransDlg::iExtractAudio(int iInternalAudio, LPCTSTR lpCount, CString &csAudioOut, int &iAudioDelayOut, TCHAR *tcLangOut, int buffersize)
{
	// 이 비교 조건은 원래는 외부에 있어야 함
	// 추후 이 비교를 없애는 방법을 고민해볼 것
	if (!isAudioExtractable(l_mediainfo, iInternalAudio)) return 1;

	CString csCount(lpCount);
	CString csParam;
	iAudioDelayOut = l_mediainfo.iAudioDelay[iInternalAudio];

	// 내부에서 일단 오디오를 추출
	// 비디오 포맷에 따라 다르게 해야 함
	csAudioOut.Format(_T("%saudioextr_%03d."), static_cast<LPCTSTR>(csTempPath83), iInternalAudio);

	LPCTSTR lpExtAdded;
	l_mediainfo.getDefaultAudioExt(l_mediainfo.afAudioFormat[iInternalAudio], lpExtAdded);
	csAudioOut += lpExtAdded;

	checkTempFile(csAudioOut);

	CSmallUtilz4iTrans::DeleteSomeFile(csAudioOut, m_iKeepTempFiles==1);
	LPCTSTR fileType;
	l_mediainfo.getDefaultContainerType(l_mediainfo.ffFileFormat, fileType);
	LPCTSTR appNameLong = static_cast<LPCTSTR>(csFFMpegLong);
	LPCTSTR appNameShort = static_cast<LPCTSTR>(csFFMpeg);

	csParam.Format(_T("-i \"%s\" -map 0:%d -vn -acodec copy \"%s\""), static_cast<LPCTSTR>(l_csSrc83), l_mediainfo.iAudioStreamOrder[iInternalAudio], static_cast<LPCTSTR>(csAudioOut));

	m_clcFiles.SetItemText(l_i, ciCOLUMN_RESULTMS, _T("오디오 추출중")+csCount);
	WriteLog(_T("Extracting audio from "), fileType+csCount);

	AddTempList(csaTempFiles, csAudioOut);
	WriteHDDSizeToLog(csAudioOut);

	l_bThreadStopped = FALSE;

	DWORD dwAudioTimer = GetTickCount();

	CStringArray csaAppNamesLong;
	csaAppNamesLong.Add(appNameLong);
	RunApp(appNameShort, csParam, csaAppNamesLong);

	dwAudioTimer = GetTickCount()-dwAudioTimer;
	CString csAudioTimer;
	csAudioTimer.Format(_T(" (%s)"), tcGetElapsedTime(dwAudioTimer));

	if (l_bThreadStopped) {
		WriteLog(_T("Audio extraction"), _T(" stopped")+csCount+csAudioTimer);

		return 0;
	}
	WriteLog(_T("Audio extraction"), tcFinished+csCount+csAudioTimer);

	if (CSmallUtilz4iTrans::IsFileorfolder(csAudioOut) && CSmallUtilz4iTrans::GetFileSize(csAudioOut)>=100) {
		_tcsncpy_s(tcLangOut, buffersize, l_mediainfo.tcAudioLang+iInternalAudio*3, 2);

		return 2;
	}
	
	return 1;
}


// 0: 확인 성공, 1: 확인 실패, 2: 스레드 중단
int CiTransDlg::iGetMaxVolume(LPCTSTR lpSrc, int streamorder, double * pdMaxDecibel)
{
	// 소스가 없으면 당연히 실패
	if (!CSmallUtilz4iTrans::IsFileorfolder(lpSrc) || !pdMaxDecibel) {
		return 1;
	}
	// RunApp()->Run()에서도 처리되지만, 만약을 대비해 삽입
	if (pdMaxDecibel) *pdMaxDecibel = 0.0;

	// ffmpeg -i test.avi -af "volumedetect" -vn -f null NUL

	CString csParam;
	csParam.Format(_T("-i \"%s\" -map 0:%d -af \"volumedetect\" -vn -f null NUL"), lpSrc, streamorder);

	WriteLog(_T("Checking max volume"));

	l_bThreadStopped = FALSE;

	CStringArray csaAppNamesLong;
	csaAppNamesLong.Add(csFFMpegLong);
	RunApp(csFFMpeg, csParam, csaAppNamesLong, true, pdMaxDecibel);
	if (l_bThreadStopped) {
		WriteLog(_T("Checking max volume"), tcStopped);
		return 2;
	}
	csLogMsg.Format(_T("max volume: %.2fdB"), *pdMaxDecibel);
	WriteLog(csLogMsg);
	return 0;
}

// 0: 변환 성공, 1: 변환 실패, 2: 스레드 중단
int CiTransDlg::iEncode2AAC(LPCTSTR lpSrc, bool bIsExternal, CMediaInfo &mediainfo, int iAudioPos, int &iDelay, int iCount, LPCTSTR lpCount , CString & csAAC)
{
	static int iConvertedAudioCount=0;

	if (!CSmallUtilz4iTrans::IsFileorfolder(lpSrc) || ((mediainfo.iAudioCount)<=iAudioPos)) {
		csAAC = _T("");
		return 1;
	}

	double dMaxVolume=0.0;
	CString csCount(lpCount);
	if (o_bNormalizeAudio) {
		m_clcFiles.SetItemText(l_i, ciCOLUMN_RESULTMS, _T("음량 확인")+csCount+_T(" 중(→ AAC)"));
		if (iGetMaxVolume(lpSrc, mediainfo.iAudioStreamOrder[iAudioPos], &dMaxVolume) == 2) return 2;
		dMaxVolume = o_dNormalizeAudioInDB-dMaxVolume;
	}

	csAAC.Format(_T("%stemp_%04d.aac"), static_cast<LPCTSTR>(csTempPath83), iConvertedAudioCount++);
	checkTempFile(csAAC);

	bool bDownMix = !o_bDontDownmixAudio;
	if (mediainfo.iChannels[iAudioPos]<=2) bDownMix=false;

	//VIDEO
	// ffmpeg -i test.avi -map 0:1 -vn -acodec pcm_s32le -ac 6 -f wav - | neroAacEnc -lc -if - -ignorelength -of out.aac
	//AUDIO
	// ffmpeg -i test.dts -map 0:1 -vn -acodec pcm_s32le -ac 6 -f wav - | neroAacEnc -lc -if - -ignorelength -of out.aac
	//DTS/AC3/FLAC/WMA/WAV(PCM) 외에는 one-shot에 downmix가 안 되는 것 같음
	// ffmpeg -i test.avi -map 0:1 -vn -acodec pcm_s32le -f wav - | ffmpeg -i - -ac 2 -f wav - | neroAacEnc -lc -if - -ignorelength -of out.aac
	//SS (accurate seeking)
	// ffmpeg -i test.avi -map 0:1 -vn -ss 00:03:00.25 -acodec pcm_s32le -f wav - | ffmpeg -i - -ac 2 -f wav - | neroAacEnc -lc -if - -ignorelength -of out.aac
	//change volume
	// ffmpeg -i input.wav -af "volume=3.0dB" output.wav
	CString csParam;
	iDelay = mediainfo.iAudioDelay[iAudioPos];
	csParam.Format(_T("-i \"%s\" -map 0:%d -vn "), lpSrc, mediainfo.iAudioStreamOrder[iAudioPos]);
	if (iDelay<0) {
		int HH, MM, SS, MS;
		MS = -iDelay;
		SS = MS/1000;
		MS %= 1000;
		MM = SS/60;
		SS %= 60;
		HH = MM/60;
		MM %= 60;

		csParam.AppendFormat(_T("-SS %02d:%02d:%02d.%03d "), HH, MM, SS, MS);
		iDelay = 0;
	}
	if (o_bNormalizeAudio && dMaxVolume>DOUBLE_MIN_LIMIT) {
		csParam.AppendFormat(_T("-af \"volume=%.2fdB\" "), dMaxVolume);
	}
	csParam += _T("-acodec pcm_s32le -f wav - | ");

	if (bDownMix) {
		csParam.AppendFormat(_T("\"%s\" -i - -ac 2 -f wav - | "), static_cast<LPCTSTR>(csFFMpeg));
	}

	csParam.AppendFormat(_T("\"%s\" -lc "), static_cast<LPCTSTR>(csNeroAAC));

	if (mediainfo.iChannels[iAudioPos]<=2 || bDownMix) {
		static int bitRates[] = {	136000,	192000,	256000,	320000, 0	};
		int bitRateNow = bitRates[o_iAudioBR];

		if (bitRateNow) {
			csParam.AppendFormat(_T("-br %d "), bitRateNow);
		}
	}
	csParam.AppendFormat(_T("-if - -ignorelength -of \"%s\""), static_cast<LPCTSTR>(csAAC));

	{
		AddTempList(csaTempFiles, csAAC);

		csLogMsg.Format(_T("Converting %s audio to AAC"), bIsExternal?_T("external"):_T("internal"));
		WriteLog(csLogMsg, lpCount);
		WriteHDDSizeToLog(csAAC);

		m_clcFiles.SetItemText(l_i, ciCOLUMN_RESULTMS, tcAudioConversionK+csCount+_T(" 중(→ AAC)"));
		l_bThreadStopped = FALSE;

		DWORD dwAudioTimer = GetTickCount();

		CStringArray csaAppNamesLong;
		csaAppNamesLong.Add(csFFMpegLong);
		csaAppNamesLong.Add(csNeroAACLong);
		RunApp(csFFMpeg, csParam, csaAppNamesLong);

		dwAudioTimer = GetTickCount()-dwAudioTimer;
		CString csAudioTimer;
		csAudioTimer.Format(_T(" (%s)"), tcGetElapsedTime(dwAudioTimer));

		if (l_bThreadStopped) {
			WriteLog(tcAudioConversion, csCount+tcStopped+csAudioTimer);

			return 2;
		}

		if (!CSmallUtilz4iTrans::IsFileorfolder(csAAC)) {
			WriteLog(tcAudioConversion, csCount+tcFailed+csAudioTimer);

			m_clcFiles.SetItemText(l_i, ciCOLUMN_RESULTMS, tcAudioConversionK+csCount+_T(" 실패"));

			return 1;
		}
		WriteLog(tcAudioConversion, csCount+tcFinished+csAudioTimer);
		WriteLog(_T("AAC "), (tcFileName + (_T(": `")+csAAC))+_T("`"));
	}

	CMediaInfo audioinfo(csMediaInfo, csMediaInfoLong, csFFMpeg, csFFMpegLong, csTempPath83, m_iKeepTempFiles, o_bIgnoreUTC);
	audioinfo.GetMediaInfo(csAAC, false);
	if (audioinfo.iAudioCount && audioinfo.iAudioDelay[0]) {
		csLogMsg.Format(_T("audio delay changed: %dms → %dms"), iDelay, iDelay+audioinfo.iAudioDelay[0]);
		WriteLog(csLogMsg);
		iDelay += audioinfo.iAudioDelay[0];
	}

	return 0;
}

#define PCMWAVEHEADERSIZE 44
#define BUFFERSIZE 65536

CString CiTransDlg::csMakeValidPCM(LPCTSTR lpAudio, int iSamplingRate, int iBitDepth, int iChannels)
{
	if (!CSmallUtilz4iTrans::IsFileorfolder(lpAudio)) return _T("");
	static BYTE bHeader[PCMWAVEHEADERSIZE];
	ZeroMemory(bHeader, PCMWAVEHEADERSIZE);

	FILE *f;
	if (_tfopen_s(&f, lpAudio, _T("rb")) || !f) return _T("");
	fread(bHeader, 1, PCMWAVEHEADERSIZE, f);
	fclose(f);

	if (!(strncmp((const char *)bHeader, "RIFF", 4) | strncmp((const char *)bHeader+8, "WAVE", 4))) {
		return lpAudio;
	}

	UINT iBlockAlign = ((iBitDepth+7)/8)*iChannels;
	UINT iSubchunk2Size = (CSmallUtilz4iTrans::GetFileSize(lpAudio)/iBlockAlign)*iBlockAlign;

	CString csRet(lpAudio);
	csRet += _T(".wav");

	long int *pi;
	short int *psi;

	pi = (long int *)(bHeader);
	*(pi++) = 0x46464952;	// RIFF
	*(pi++) = 36 + iSubchunk2Size;
	*(pi++) = 0x45564157;	// WAVE
	*(pi++) = 0x20746d66;	// fmt
	*(pi++) = 0x00000010;	// 16

	psi = (short int *)pi;
	*(psi++) = 1;
	*(psi++) = (short int)iChannels;

	pi = (long int *)psi;
	*(pi++) = iSamplingRate;
	*(pi++) = (iSamplingRate * iBlockAlign);

	psi = (short int *)pi;
	*(psi++) = (short int)iBlockAlign;
	*(psi++) = (short int)iBitDepth;

	pi = (long int *)psi;
	*(pi++) = 0x61746164;	// data
	*pi = iSubchunk2Size;

	FILE *fw;
	if (!_tfopen_s(&fw, csRet, _T("wb")) && fw) {
		fwrite(bHeader, 1, PCMWAVEHEADERSIZE, fw);

		static BYTE bBuf[BUFFERSIZE];
		int iRemain = iSubchunk2Size;

		if (!_tfopen_s(&f, lpAudio, _T("rb")) && f) {
			while (iRemain) {
				int iRead = fread(bBuf, 1, BUFFERSIZE, f);
				if (!iRead) break;
				fwrite(bBuf, 1, iRead, fw);
				iRemain -= iRead;
			}

			fclose(f);
		}
		fclose(fw);
	}

	return csRet;
}

BOOL CiTransDlg::bIsPCM(LPCTSTR lpFile, bool bMonoOrStereo)
{
	FILE *f;
	if (_tfopen_s(&f, lpFile, _T("rb")) || !f) return FALSE;
	BYTE b[24];
	if (fread(b, 1, 24, f)<24) {
		fclose(f);
		return FALSE;
	}

	fclose(f);
	return (
		!(strncmp((const char *)b, "RIFF", 4) | strncmp((const char *)b+8, "WAVE", 4) | strncmp((const char *)b+12, "fmt ", 4) | b[17] | b[18] | b[19]) &&

		(
		(bMonoOrStereo && ((b[20] == 1) && (b[21] == 0) && ((b[22] == 1) || (b[22] == 2)) && (b[23] == 0))) ||
		(!bMonoOrStereo && (
		((b[20] == 1) && (b[21] == 0)) ||
		((b[20] == 0xfe) && (b[21] == 0xff))
		))
		)
		);
}

void CiTransDlg::makeNewSrtName(int subno, int trackno, CString &csNewSrt)
{
	CString csLang;
	if (l_mediainfo.tcSubLang[subno*3]) {
		csLang.Format(_T("_%c%c"), l_mediainfo.tcSubLang[subno*3], l_mediainfo.tcSubLang[subno*3+1]);
	}

	csNewSrt.Format(_T("%s%d%s.srt"), static_cast<LPCTSTR>(l_csDstSrtHeader), trackno, static_cast<LPCTSTR>(csLang));
}

CString CiTransDlg::Srt2Ttxt(CString csSrt)
{
	static CString csTemp;
	csTemp = _T("-ttxt \"") + csSrt + _T("\"");

	CStringArray csaAppNamesLong;
	csaAppNamesLong.Add(csMP4BoxLong);
	RunApp(csMP4Box, csTemp, csaAppNamesLong);

	csSrt = csSrt.Left(csSrt.GetLength()-4);
	csSrt += _T(".ttxt");

	if (CSmallUtilz4iTrans::IsFileorfolder(csSrt)) return csSrt;
	else return _T("");
}

BOOL CiTransDlg::bIsFileEnglishText(LPCTSTR lpFilename, bool bCheckAmp)
{
	FILE *f;
	if (_tfopen_s(&f, lpFilename, _T("rb")) || !f) return FALSE;

	fseek(f, 0, SEEK_END);
	fpos_t flen;
	fgetpos(f, &flen);

	BYTE *b = new BYTE[int(flen)];

	fseek(f, 0, SEEK_SET);
	fread(b, 1, int(flen), f);
	fclose(f);

	int begin=0;
	if (flen>2) {
		if (b[0]==0xef && b[1]==0xbb && b[2]==0xbf) {
			begin = 3;
		}
	}

	int count=0;
	for (int i=begin; i<int(flen); i++) {
		if ((b[i] & 0x80) || (bCheckAmp && b[i]=='&')) {
			count++;
		}
	}
	delete []b;

	return ((double(count)/int(flen))<DOUBLE_MIN_LIMIT);
}

//글꼴은 Malgun Gothic Bold로 변경
bool CiTransDlg::changeAssFontSize(LPCTSTR lpAss, unsigned char size)
{
	FILE *f;
	if (_tfopen_s(&f, lpAss, _T("rb")) || !f) return false;

	// 정규식을 쓸 수 있으면 한 방에 찾으련만...
	// Style: Default,Arial,25,

	int len = CSmallUtilz4iTrans::GetFileSize(lpAss);
	char *ass = new char[len];
	fread(ass, 1, len, f);
	fclose(f);

	static char fontsize[MAX_PATH];
	int fontsizelen = sprintf_s(fontsize, MAX_PATH, "Malgun Gothic Bold,%u", size);

	// 먼저 "[V4"를 찾고
	// 그 다음에 "Style:"을 찾은 뒤
	// "," 세 개 찾음

	int i;	// int i를 밖에 선언한 이유는 각 루프를 이동하며 i는 그대로 사용하기 때문
	int comma1, comma2;
	bool bFound=false;
	for (i=0; i<len; i++) {
		if (strncmp(ass+i, "[V4", 3)==0) {
			bFound=true;
			break;
		}
	}
	if (!bFound) goto STOPLOOP;

	bFound=false;
	for (; i<len; i++) {
		if (strncmp(ass+i, "Style:", 6)==0) {
			bFound=true;
			break;
		}
	}
	if (!bFound) goto STOPLOOP;

	bFound=false;
	for (; i<len; i++) {
		if (ass[i]==',') {
			comma1=i;
			bFound=true;
			break;
		}
	}
	if (!bFound) goto STOPLOOP;

	bFound=false;
	for (++i; i<len; i++) {
		if (ass[i]==',') {
			bFound=true;
			break;
		}
	}
	if (!bFound) goto STOPLOOP;

	bFound=false;
	for (++i; i<len; i++) {
		if (ass[i]==',') {
			comma2=i;
			bFound=true;
			break;
		}
	}
	if (!bFound) goto STOPLOOP;

	// 0~comma1 쓰기
	// Malgun Gothic Bold,fontsize 쓰기
	// comma2~len-1 쓰기

	if (_tfopen_s(&f, lpAss, _T("wb")) || !f) {
		delete []ass;
		return false;
	}

	fwrite(ass, 1, comma1+1, f);
	fwrite(fontsize, 1, fontsizelen, f);
	fwrite(ass+comma2, 1, len-comma2, f);
	fclose(f);

	delete []ass;
	return true;

STOPLOOP:
	delete []ass;
	return false;
}

void CiTransDlg::encode2H264(CString csDst, int iPass, int iProfile2, LPCTSTR lpFPS, int iBitRate, int iRotate, LPCTSTR lpAssSub)
{

	if (redir) return;
	if (csFFMpeg.Find(_TCHAR('\\'))<0) return;

	//iProfile2
	// 0: Very Slow
	// 1: Slow
	// 2: Default
	// 3: Very Fast

	// Batch file 생성 (csBatch)
	LPCTSTR lpApp = static_cast<LPCTSTR>(csFFMpeg);
	LPCTSTR lpAppName = CSmallUtilz4iTrans::getAppNameOnly(lpApp);

	CString csBatch;
	CSmallUtilz4iTrans::makeBatchFileName(csBatch, csTempPath83, tcRunApp);
	csaTempFiles.Add(csBatch);

	FILE *f;
	if (_tfopen_s(&f, csBatch, _T("wt")) || !f) return;

	CString cs;

	cs.Format(_T("pushd \"%s\"\nattrib -s -r -h \"%s\"\ndel \"%s\"\n"), static_cast<LPCTSTR>(csTempPath), static_cast<LPCTSTR>(csDst), static_cast<LPCTSTR>(csDst));
	printfuni2ansi(f, static_cast<LPCTSTR>(cs));

	bool bDeinterlace = o_bDeinterlace || (m_iConversionMode>=2) && (l_mediainfo.iInterlace == 1);

	CString csTranspose;
	CString csSize;

	csSize.Format(_T("-s %dx%d"), l_iWidthNew, l_iHeightNew);
	switch (iRotate) {
	case 2:
		csTranspose = _T("-vf \"vflip,hflip\" ");
		break;

	case 1:
	case 3:
		csTranspose.Format(_T("-vf \"transpose=%d\" "), (iRotate+1)>>1);
		csSize.Format(_T("-s %dx%d"), l_iHeightNew, l_iWidthNew);
		break;
	}

	static LPCTSTR lpPass[] = {
		_T(""),	_T("-pass 1 "), _T("-pass 2 ")
	};

	CString csX264Profile(_T("-partitions +parti8x8+parti4x4+partp8x8+"));
	//0123456789012345678901234567890123456789
	//          1         2         3
	{
		if (iPass == 1) {
			csX264Profile.Replace(_TCHAR('+'), _TCHAR('-'));
		} else if (!iProfile2) {
			csX264Profile += _T("partp4x4+");
		}

		csX264Profile += _T("partb8x8 -me_method ");
		csX264Profile += (iPass == 1)?_T("dia"):((iProfile2<2)?_T("umh"):_T("hex"));

		int profileInArray = (iProfile2<2)?(iProfile2*2+iPass-1):(4+iPass);
		// 0: very slow 1st pass
		// 1: very slow 2nd pass
		// 2: slow 1st pass
		// 3: slow 2nd pass
		// 4: default
		// 5: very fast 1st pass
		// 6: very fast 2nd pass

		static signed char subq[] = {2,	10,	2,	8,	7,	2,	2};
		static signed char refs[] = {1,	16,	1,	5,	3,	1,	1};
		LPCTSTR lpTrellis = _T("0201100");

		CString cs;
		bool bVerySlowOrSlow = (iProfile2<2);
		cs.Format(_T(" -subq %d -me_range %d -b_strategy %c -bf %c -refs %d -direct-pred %c -trellis %c -wpredp %c "),
			subq[profileInArray],
			iProfile2?16:24,
			bVerySlowOrSlow?_TCHAR('2'):_TCHAR('1'),
			iProfile2?_TCHAR('3'):_TCHAR('8'),
			refs[profileInArray],
			bVerySlowOrSlow?_TCHAR('3'):_TCHAR('1'),
			lpTrellis[profileInArray],
			(iProfile2<3)?_TCHAR('2'):_TCHAR('0')
			);

		csX264Profile += cs;

		if (bVerySlowOrSlow) {
			cs.Format(_T("-rc_lookahead %d "), iProfile2?50:60);
			csX264Profile += cs;
		}

		if (iProfile2!=2) {
			csX264Profile += _T("-b-pyramid 1 ");
		}

		if (iProfile2==3) {
			csX264Profile += _T("-mbtree 1 ");
		}
	}
	csX264Profile.TrimRight();

	CString csFFMpegTemp(csFFMpeg);
	if (csFFMpeg.Find(_TCHAR(' '))>=0) {
		csFFMpegTemp = _TCHAR('\"')+csFFMpeg+_TCHAR('\"');
	}

	//FFMpeg에서 -vf ass= 옵션은 좀 특이함
	// -vf "ass='c\:/folder1/folder2/file.ass' 형식
	CString csExtSub;
	if (lpAssSub && (*lpAssSub)) {
		csExtSub.Format(_T("-vf \"ass=\'%s\'\" "), lpAssSub);
		csExtSub.Replace(_TCHAR('\\'), _TCHAR('/'));
		csExtSub.Replace(_T(":"), _T("\\:"));
	}

	// VFR과 2-pass는 함께 쓰일 수 없음 ('13.9.8 확인 완료)
	// 즉, 2-pass는 무조건 CFR임
	// 2-pass의 로그 파일명은 기본 이름을 쓰지 않음 -passlogfile

	CString csFPS(lpFPS);
	csFPS.TrimRight();
	csFPS = (csFPS.IsEmpty() || (l_mediainfo.bVFR && !iPass))?_T(""):_T("-r ")+csFPS+_T(" ");

	CString csPass(lpPass[iPass]);
	if (iPass) csPass.AppendFormat(_T("-passlogfile %s "), tc2passLogFilename);

	cs.Format(
		_T("%s %s-i \"%s\" %s-vsync %s %s%s -an %s-vcodec libx264 %s -b:v %dk %s%s\"%s\""),
		static_cast<LPCTSTR>(csFFMpegTemp), ffmpegThreadsTstr, static_cast<LPCTSTR>(l_csSrc83), bDeinterlace?_T("-deinterlace "):_T(""),
		/* -vsync */(l_mediainfo.bVFR && !iPass)?_T("vfr"):_T("cfr"), static_cast<LPCTSTR>(csTranspose), static_cast<LPCTSTR>(csSize), static_cast<LPCTSTR>(csPass),
		/* libx264 */static_cast<LPCTSTR>(csX264Profile), (iBitRate+500)/1000, static_cast<LPCTSTR>(csFPS), static_cast<LPCTSTR>(csExtSub), static_cast<LPCTSTR>(csDst)
		);

	printfuni2ansi(f, static_cast<LPCTSTR>(cs));

	WriteLog(_T("`"), cs+_T("`"));

	fclose(f);

	// 실행
	CStringArray csaAppNames;
	csaAppNames.Add(lpAppName);
	redir = new CRedirect(csBatch, csaAppNames, reinterpret_cast<CStatusBarCtrl*>(GetDlgItem(IDC_STATUSBAR)), NULL, _T("processing..."), l_mediainfo.iFrameCount);
	redir->Run();

	// Batch file 삭제
	if (m_iKeepTempFiles<2)
		CSmallUtilz4iTrans::DeleteSomeFile(csBatch, m_iKeepTempFiles==1);

	SetStatusBarText(2, _T(""));
	delete redir;
	redir = NULL;

}

TCHAR * CiTransDlg::tcGetElapsedTime(DWORD dwTimeElapsed, bool miliSec)
{
	// 12:00:00.000
	// 012345678901
	static TCHAR tcTimeStr[14] = {0};

	int H, M, S, MS;
	CSmallUtilz4iTrans::ms2HMSMS(int(dwTimeElapsed), H, M, S, MS);

	CString miliSecStr;
	if (miliSec) {
		miliSecStr.Format(_T(".%03d"), MS);
	}

	if (H) {
		_stprintf_s(tcTimeStr, 14, _T("%d:%02d:%02d%s"), H, M, S, static_cast<LPCTSTR>(miliSecStr));
	} else {
		_stprintf_s(tcTimeStr, 14, _T("%d:%02d%s"), M, S, static_cast<LPCTSTR>(miliSecStr));
	}

	return tcTimeStr;
}

CString CiTransDlg::csGetH264MaxLevel(int iWidth, int iHeight, CString csFPS)
{
	double dFPS = _ttof(csFPS);

	csLogMsg.Format(_T("FPS: %g"), dFPS);
	WriteLog(csLogMsg);


#if 0
	// 일단 3.0 이하는 3.0으로 대동단결

	static TCHAR tcRet[12];
	_tcscpy_s(tcRet, 12, _T(":level="));

	if (
	    ((dFPS<=61.4) && (iWidth<=352) && (iHeight<=480)) ||
	    ((dFPS<=51.1) && (iWidth<=352) && (iHeight<=576)) ||
	    ((dFPS<=30.0) && (iWidth<=720) && (iHeight<=480)) ||
	    ((dFPS<=25.0) && (iWidth<=720) && (iHeight<=576))
	) {

		_tcscat_s(tcRet, 12, bNightly?_T("30"):_T("3.0"));
	} else if (
	    ((dFPS<=80.0) && (iWidth<=720) && (iHeight<=480)) ||
	    ((dFPS<=66.7) && (iWidth<=720) && (iHeight<=576)) ||
	    ((dFPS<=30.0) && (iWidth<=1280) && (iHeight<=720))
	) {
		_tcscat_s(tcRet, 12, bNightly?_T("31"):_T("3.1"));
	} else if (
	    ((dFPS<=60.0) && (iWidth<=1280) && (iHeight<=720)) ||
	    ((dFPS<=42.2) && (iWidth<=1280) && (iHeight<=1024))
	) {
		_tcscat_s(tcRet, 12, bNightly?_T("32"):_T("3.2"));
	} /* else if ( 	// 4.0은 4.1과 해상도 및 FPS가 동일함
	    ((dFPS<=68.3) && (iWidth<=1280) && (iHeight<=720)) ||
	    ((dFPS<=30.1) && (iWidth<=1920) && (iHeight<=1080)) ||
	    ((dFPS<=30.0) && (iWidth<=2048) && (iHeight<=1024))
	) {
		_tcscat_s(tcRet, 12, bNightly?_T("41"):_T("4.1"));
	} */ else {
		// 디바이스 지원 해상도를 넘어서면
		// 무조건 level 4.1이라고 대충 리턴
		// 즉, 이 앞의 4.0/4.1 확인 부분은 사실상 불필요
		_tcscat_s(tcRet, 12, bNightly?_T("41"):_T("4.1"));
	}
#endif

	// max bit rate가 범위를 벗어나는 등, 희안한 경우가 종종 발생
	// 그런데, 비디오 소스가 AVI인 경우 max bit rate를 알아낼 방법이 없음.
	// 그래서 3.x는 모두 3.2로, 4.x는 모두 4.1로 하는 것으로 변경 (2013.10.15)

	static TCHAR tcRet[12];
	_tcscpy_s(tcRet, 12, _T(":level="));

	if (
	    ((dFPS<=80.0) && (iWidth<=720) && (iHeight<=480)) ||
	    ((dFPS<=66.7) && (iWidth<=720) && (iHeight<=576)) ||
	    ((dFPS<=60.0) && (iWidth<=1280) && (iHeight<=720)) ||
	    ((dFPS<=42.2) && (iWidth<=1280) && (iHeight<=1024))
	) {
		_tcscat_s(tcRet, 12, bNightly?_T("32"):_T("3.2"));
	} else {
		_tcscat_s(tcRet, 12, bNightly?_T("41"):_T("4.1"));
	}

	return tcRet;
}


// 새로운 세상을 봤다
// http://en.wikipedia.org/wiki/Binary_GCD_algorithm#Iterative_version_in_C.2B.2B_using_ctz_.28count_trailing_zeros.29
unsigned int CiTransDlg::uiGCD(unsigned int x, unsigned int y)
{
	if (!x || !y) return (x+y);

	// __lzcnt(x) counts trailing zeros in x
	unsigned long cf2, temp;
	_BitScanForward(&cf2, x | y);

	_BitScanForward(&temp, x);
	x >>= temp;

	for (;;) {
		_BitScanForward(&temp, y);
		y >>= temp;

		if (x == y) break;
		if (x>y) std::swap(x, y);
		if (x == 1) break;

		y -= x;
	}

	return x << cf2;
}

void CiTransDlg::checkSourceFilesInFinal(CString &csMP4OptionFull)
{
	// 원래 옵션 첫 글자가 공백인 것을 명심할 것
	CString cs(csMP4OptionFull);
	cs.TrimLeft();
	csMP4OptionFull = _T("");

	CStringArray csa;

	while (true) {
		int space = findSpace(cs);
		if (cs.IsEmpty()) break;

		if (space<0) {
			csa.Add(cs);
			break;
		} else {
			CString cs0 = cs.Left(space);
			csa.Add(cs0);
			cs.Delete(0, space);
		}
	}

	int count = csa.GetSize();
	for (int i=0; i<count; i++) {
		CString cs = csa.GetAt(i);
		if (cs.CompareNoCase(_T("-add"))) {
			csMP4OptionFull += (_T(" ")+cs);
		} else {
			if (i>=(count-1)) break;
			cs = csa.GetAt(++i);
			// 이제 파일명 확인
			CString filename;
			if (cs.GetAt(0) == _TCHAR('\"')) {
				// 정상적인 따옴표 상황
				// 기본적으로 여기
				int quote = cs.Find(_TCHAR('\"'), 1);
				filename = (quote<0)?cs:cs.Left(quote);
				filename.Delete(0, 1);
			} else {
				int colon = cs.Find(_TCHAR(':'));
				filename = (colon<0)?cs:cs.Left(colon);
			}

			CString log(_T("checking source file `") + filename + _T("`: "));
			if (CSmallUtilz4iTrans::IsFileorfolder(filename)) {
				log+=_T("**OK**");
				csMP4OptionFull += (_T(" -add ") + cs);
			} else {
				log+=_T("**FAILED!**");
			}

			WriteLog(log);
		}
	}
}

void CiTransDlg::setFileDateFromCDate(LPCTSTR lpFile, CTime &ctime)
{
	CFileStatus status;

	CFile::GetStatus(lpFile, status);

	status.m_ctime = ctime;
	status.m_atime = ctime;
	status.m_mtime = ctime;

	CFile::SetStatus(lpFile, status);
}

int CiTransDlg::findSpace(CString &str)
{
	str.TrimLeft();
	if (str.IsEmpty()) return -1;
	if (str.GetAt(0) == _TCHAR('\"')) {
		int i = str.Find(_TCHAR('\"'), 1);
		return (i<0)?-1:str.Find(_TCHAR(' '), i+1);
	} else {
		return str.Find(_TCHAR(' '));
	}
}

bool CiTransDlg::checkFolders()
{
	if (!CSmallUtilz4iTrans::bIsFolderWritable(csTempPath, m_iKeepTempFiles==1)) {
		if (!CSmallUtilz4iTrans::CheckTempFolder(csTempPath, m_iKeepTempFiles==1)) {
			MessageBox(_T("임시 폴더 사용 불가!"));
			return false;
		}
	}

	if (o_bUseSaveFolder) {
		if (!CSmallUtilz4iTrans::bIsFolderWritable(csSavePath, m_iKeepTempFiles==1)) {
			MessageBox(_T("저장폴더 오류!"));
			return false;
		}

		CString csTempPathShort = csGetShortName(csTempPath);
		CString csSavePathShort = csGetShortName(csSavePath);
		csTempPathShort.TrimRight(_TCHAR('\\'));
		csSavePathShort.TrimRight(_TCHAR('\\'));
		if (csTempPathShort.CompareNoCase(csSavePathShort) == 0) {
			MessageBox(_T("저장폴더와 임시폴더가 같음!"));
			return false;
		}
	}

	return true;
}

bool CiTransDlg::checkExternalApps()
{
	bool notfound = false;

	static LPCTSTR lpToolName[] = {
		_T("ffmpeg"),		_T("mediainfo"),	_T("mp4box"),
		_T("neroaacenc"),	_T("shutdown")
	};
	for (int i=0; i<(sizeof(lpToolName)/sizeof(lpToolName[0])); i++) {
		CStringArray csa;

		int iPathOrToolOnly=0;
		if (i==2) iPathOrToolOnly = 2;
		if (i==4) iPathOrToolOnly = 1;

		int exetype = getExeNames(lpToolName[i], o_bPathFirst, iPathOrToolOnly, csa);
		if (!exetype) {
			notfound = true;
		} else {
			// FFMpeg, MediaInfo, mp4box 의 내용 확인
			switch (i) {
			case 0:
				// FFMpeg은 (자막 입힐 때는) 빌드 날짜/시간이 Jan  2 2013 20:14:49 이전 및 fonts.conf가 있는 것을 찾음
				// bBurnSubtitle이 켜져있으면 자막 가능 버전을 무조건 요구, 없으면 다 때려쳐
				// 켜져있지 않으면 그냥 처음 걸린 놈 쓰는 거임 / 여기부터

				// 버전 확인시에는 로그로 출력하지 않음
				// RunApp에서 큰따옴표 사용여부 통일 (완)
				// FFMpeg 빌드 시간 부분 확인 (완)

				{
					if (l_bBurnSubtitle) {
						// v1.0.1 static: Jan  2 2013, at: 20:14:49
						static COleDateTime ffmpegv11date(2013, 1, 2, 20, 14, 49);
						COleDateTime ffmpegdate;

						bool bFound = false;

						for (int j=0; j<csa.GetSize(); j++) {
							csFFMpegLong = csa.GetAt(j);
							csFFMpeg = csGetShortName(csFFMpegLong);

							getFFMpegBuildDate(ffmpegdate);
							if ((ffmpegdate >= ffmpegv11date) && existFFMpegFontsConf()) {
								bFound = true;
								csLogMsg.Format(_T("  : `%s` (built on %s)"), static_cast<LPCTSTR>(csFFMpegLong), static_cast<LPCTSTR>(ffmpegdate.Format(_T("%Y-%m-%d %H:%M:%S"))));
								WriteLog(csLogMsg);
								break;
							}
						}

						if (!bFound) {
							csFFMpegLong = csFFMpeg = _T("");
							notfound = true;
						}
					} else {
						assign1stAndWriteLog(csFFMpegLong, csFFMpeg, csa, lpToolName[0]);
					}
				}
				break;

			case 1:
				// MediaInfo는 CLI 버전만 선택하며
				// 0.7.64 이후의 버전만 사용함
				{
					bool bFound = false;
					WORD vers[4];
					CString csInternalName;

					for (int j=0; j<csa.GetSize(); j++) {
						csMediaInfoLong = csa.GetAt(j);
						CSmallUtilz4iTrans::getVersionNoAndInternalName(csMediaInfoLong, vers, csInternalName);

						if (
							(vers[0]>0) || (vers[1]>7) || ((vers[1]==7) && (vers[2]>=64)) &&
							(csInternalName.Find(_T("CLI")) >= 0)
							) {
							csMediaInfo = csGetShortName(csMediaInfoLong);
							bFound = true;

							csLogMsg.Format(_T("  : `%s`"), static_cast<LPCTSTR>(csMediaInfoLong));
							WriteLog(csLogMsg);

							break;
						}
					}

					if (!bFound) {
						csMediaInfoLong = csMediaInfo = _T("");
						notfound = true;
					}
				}
				break;

			case 2:
				// mp4box는 첫번째 것이 tools 아래에 있는 것인지 확인
				bNightly = (exetype == 2);
				assign1stAndWriteLog(csMP4BoxLong, csMP4Box, csa, lpToolName[2]);
				break;

			case 3:
				// NeroAACEnc
				assign1stAndWriteLog(csNeroAACLong, csNeroAAC, csa, lpToolName[3]);
				break;

			case 4:
				// shutdown
				assign1stAndWriteLog(csShutdownLong, csShutdown, csa, lpToolName[4]);
				break;
			}
		}
	}

	if (notfound) {
		WriteLog(_T("File check"), tcFailed);
		MessageBox(_T("주요 파일이 없음!!"));
	}

	return !notfound;
}

// CStringArray에 저장함
// 그리고, 리스트 첫번째 값에 대해 다음과 같이 리턴함
// 0: EXE 없음. 젠장
// 1: EXE 발견, tools에서
// 2: EXE 발견, PATH에서
int CiTransDlg::getExeNames(LPCTSTR lpToolName, BOOL bPathFirst, int iPathOrToolsOnly, CStringArray &csaExe)
{
	csaExe.RemoveAll();
	int ret = 0;

	CString csExe;
	csExe.Format(_T("%s%s\\%s.exe"), static_cast<LPCTSTR>(l_csToolsRoot), lpToolName, lpToolName);

	switch (iPathOrToolsOnly) {
	case 1:	// path only
		if (getExeNamesInPath(lpToolName, csaExe)) {
			ret = 2;
		}
		break;

	case 2:	// tools only
		if (CSmallUtilz4iTrans::IsFileorfolder(csExe)) {
			csaExe.Add(csExe);
			ret = 1;
		}
		break;

	default:
		if (bPathFirst) {
			if (getExeNamesInPath(lpToolName, csaExe)) {
				ret = 2;
			}
		}

		if (CSmallUtilz4iTrans::IsFileorfolder(csExe)) {
			csaExe.Add(csExe);
			if (!ret) ret = 1;
		}

		if (!bPathFirst) {
			if (getExeNamesInPath(lpToolName, csaExe)) {
				if (!ret) ret = 2;
			}
		}
	}

	int count = csaExe.GetSize();
	CString csLog;
	if (count) {
		csLog.Format(_T("%d `%s.exe`%s found"), count, lpToolName, (count>1)?_T("s"):_T(""));
	} else {
		csLog.Format(_T("`%s.exe` **not found**"), lpToolName);
	}

	WriteLog(csLog);
	return ret;
}

bool CiTransDlg::getExeNamesInPath(LPCTSTR lpToolExe, CStringArray &csaExe)
{
	int pos=0;
	int count=0;
	while (true) {
		CString now;
		if (!AfxExtractSubString(now, csSystemPath, pos++, _TCHAR(';'))) break;
		now.TrimLeft();
		now.TrimRight(_T("\\ "));

		now = now+_TCHAR('\\')+lpToolExe+_T(".exe");
		if (CSmallUtilz4iTrans::IsFileorfolder(now)) {
			csaExe.Add(now);
			count++;
		}
	}

	return (count>0);
}

// COleDateTime oletime;
// oletime.ParseDateTime(_T("Jan  8 2013 16:10:57"));
// 
// int a[10];
// a[0] = oletime.GetYear();
// a[1] = oletime.GetMonth();
// a[2] = oletime.GetDay();

bool CiTransDlg::getFFMpegBuildDate(COleDateTime &cDate)
{
	CString csVersion = csTempPath83+_T("FFMpegVer.txt");
	CString csParam = _T("-version > \"")+csVersion+_T("\"");

	CStringArray csaAppNamesLong;
	csaAppNamesLong.Add(csFFMpegLong);
	RunApp(csFFMpeg, csParam, csaAppNamesLong, false);

	FILE *f;
	if (_tfopen_s(&f, csVersion, _T("rt")) || !f) return false;

	TCHAR buffer[MAX_PATH];
	bool bFound = false;

	while (_fgetts(buffer, MAX_PATH, f)) {
		CString cs(buffer);

		if (cs.Left(8).CompareNoCase(_T("built on"))) continue;
		cs.Delete(0, 8);
		cs.TrimLeft();

		int with = cs.Find(_T("with"));
		if (with<0) continue;

		cs = cs.Left(with);
		cs.TrimRight();

		if (cDate.ParseDateTime(cs)) {
			bFound = true;
			break;
		}
	}
	fclose(f);

	CSmallUtilz4iTrans::DeleteSomeFile(csVersion, m_iKeepTempFiles==1);
	return bFound;
}

bool CiTransDlg::existFFMpegFontsConf()
{
	int pos = csFFMpegLong.ReverseFind(_TCHAR('\\'));
	if (pos<0) return false;

	return (CSmallUtilz4iTrans::IsFileorfolder(csFFMpegLong.Left(pos+1)+_T("fonts\\fonts.conf")));
}

void CiTransDlg::assign1stAndWriteLog(CString &csLong, CString &csShort, CStringArray &csa, LPCTSTR name)
{
	csLong = csa.GetAt(0);
	csShort = csGetShortName(csLong);

	csLogMsg.Format(_T("  : `%s`"), static_cast<LPCTSTR>(csLong));
	WriteLog(csLogMsg);
}

// 실패한 경우만  false
bool CiTransDlg::step1_prepareJob()
{
	l_csSizeStr=_T("");

	m_clcFiles.EnsureVisible(l_i, FALSE);
	m_clcFiles.SetItemText(l_i, ciCOLUMN_RESULTMS, _T("변환 준비중"));

	CString csCount;
	csCount.Format(_T("%d/%d"), l_i+1, l_iCount);
	SetStatusBarText(0, const_cast<LPTSTR>(static_cast<LPCTSTR>(csCount)));

	WriteLog(_T("Preparing job: "), csCount);

	l_dwTime = GetTickCount();
	csLogMsg.Format(_T("GetTickCount() result: %u"), l_dwTime);
	WriteLog(csLogMsg);

	m_clcFiles.CheckExternalFiles(l_i);

	l_mediainfo.initMediaInfo(csMediaInfo, csMediaInfoLong, csFFMpeg, csFFMpegLong, csTempPath83, m_iKeepTempFiles, o_bIgnoreUTC);

	l_csSrc = m_clcFiles.GetItemText(l_i, ciCOLUMN_PATHNAME) + m_clcFiles.GetItemText(l_i, ciCOLUMN_FILENAME);
	l_iExtLen = l_csSrc.GetLength()-l_csSrc.ReverseFind(_TCHAR('.'))-1;
	l_csExt = l_csSrc.Right(l_iExtLen);
	l_csSrcNoExt = l_csSrc.Left(l_csSrc.GetLength()-l_iExtLen-1);

	csLogMsg.Format(_T("Source %s: `%s`"), tcFileName, static_cast<LPCTSTR>(l_csSrc));
	WriteLog(csLogMsg);

	l_csCover = l_mediainfo.csCover;
	{
		if (CSmallUtilz4iTrans::IsFileorfolder(l_csCover)) {
			AddTempList(csaTempFiles, l_csCover);
		}
		
		CString cs = m_clcFiles.GetItemText(l_i, ciCOLUMN_EXTCOVER);
		if (cs.GetLength()) l_csCover.Format(_T("%s.%s"), static_cast<LPCTSTR>(l_csSrcNoExt), static_cast<LPCTSTR>(cs));
	}

	l_csSrc83 = csGetShortName(l_csSrc);

	if (!CSmallUtilz4iTrans::IsFileorfolder(l_csSrc83)) {
		WriteLog(_T("reading source file"), tcFailed);
		m_clcFiles.SetItemText(l_i, ciCOLUMN_RESULTMS, _T("원본 읽기 오류!"));
		return false;
	}
// 0~5: 국적
// 6: 그외
// 7~15: #1~#9
// 16: 전체

	static TCHAR tcLangTableInt[7][3] = {_T("ko"), _T("en"), _T("ja"), _T("de"), _T("fr"), _T("ru"), _T("**")};
	l_mediainfo.GetMediaInfo(l_csSrc83, true, (o_bAudioOrder)?tcLangTableInt[(o_iAudioOrder<7)?o_iAudioOrder:6]:NULL);

	
	if (l_mediainfo.csCover.GetLength()) {
		csLogMsg.Format(_T("art extracted: `%s`"), static_cast<LPCTSTR>(l_mediainfo.csCover));
		WriteLog(_T("Cover "), csLogMsg);
	}

	if (CSmallUtilz4iTrans::IsFileorfolder(l_csCover)) {
		csLogMsg.Format(_T("%s: `%s`"), tcFileName, static_cast<LPCTSTR>(l_csCover));
		WriteLog(_T("Cover "), csLogMsg);
		
		l_csCover = csGetShortName(l_csCover);
	} else {
		l_csCover = _T("");
	}

	///////////////////////////////
	// 오디오 관련 테이블들 생성 //
	///////////////////////////////

	// 오디오 언어 기록 테이블
	// 이 테이블들은 소스의 데이터를 기록하기 위한 것이 아님
	// 추출할 오디오의 데이터를 기록하는 것임

	l_csaAAC.RemoveAll();
	l_csaAACOption.RemoveAll();
	ZeroMemory(l_iAACDelay, sizeof(l_iAACDelay[0])*MAXAUDSUBCOUNT);

	l_csaAC3.RemoveAll();
	l_csaAC3Option.RemoveAll();
	ZeroMemory(l_iAC3Delay, sizeof(l_iAC3Delay[0])*MAXAUDSUBCOUNT);

	l_csExtAudioFilename = _T("");

	////////////////////////////////////
	// 오디오 관련 테이블들 생성 종료 //
	////////////////////////////////////

	l_csFileInfo=_T("");
	
	bool bInvalidExt = false;
	
	LPCTSTR lpFileType;
	l_mediainfo.getDefaultContainerType(l_mediainfo.ffFileFormat, lpFileType);

	switch (l_mediainfo.ffFileFormat) {
	case FFMT_NONE:
		WriteLog(_T("reading source file"), tcFailed);
		m_clcFiles.SetItemText(l_i, ciCOLUMN_RESULTMS, _T("파일 읽기 오류"));
		return false;

	case FFMT_UNKNOWN:
		WriteLog(_T("cannot identify"), _T(" file format"));
		m_clcFiles.SetItemText(l_i, ciCOLUMN_RESULTMS, _T("변환 불가 포맷"));
		return false;

	}

	bInvalidExt = (CSmallUtilz4iTrans::findinCSAi(l_mediainfo.csaExtList, static_cast<LPCTSTR>(l_csExt))<0);
		

	if (lpFileType) {
		WriteLog(_T("container format: "), lpFileType);
	}

	if (l_mediainfo.ffFileFormat == FFMT_DIVX) {
		// DivX 포맷은 MPEG4 part2 비디오 및 mp3/AC3/PCM 오디오만 수용
		if (l_mediainfo.vfVideoFormat != VFMT_MPEG4) bInvalidExt = true;
		for (int i=0; i<l_mediainfo.iAudioCount; i++) {
			if ((l_mediainfo.afAudioFormat[i] != AFMT_MP3) &&
				(l_mediainfo.afAudioFormat[i] != AFMT_AC3) &&
				(l_mediainfo.afAudioFormat[i] != AFMT_PCM)) bInvalidExt = true;
		}
	}

	if (bInvalidExt) {
		WriteLog(_T("invalid extension: "), static_cast<LPCTSTR>(l_csExt));
		m_clcFiles.SetItemText(l_i, ciCOLUMN_RESULTMS, tcExtErrorK);
		return false;
	}

	if (lpFileType) {
		l_csFileInfo.Format(_T("%s/"), lpFileType);
	}

	if (l_csExt.CompareNoCase(_T("divx"))==0 && (l_mediainfo.ffFileFormat==FFMT_AVI || l_mediainfo.ffFileFormat==FFMT_DIVX)) {
		// 원본 확장자가 divx인 경우
		m_clcFiles.SetItemText(l_i, ciCOLUMN_RESULTMS, _T("원본 파일 복사중"));
		CString csTempSrc = csTempPath+_T("tempdivxfile.avi");
		if (!CopyFile(l_csSrc, csTempSrc, FALSE)) {
			WriteLog(_T(".divx to .avi file copy"), tcFailed);
			m_clcFiles.SetItemText(l_i, ciCOLUMN_RESULTMS, _T("임시 파일 생성 실패!"));
			return false;
		}

		AddTempList(csaTempFiles, csTempSrc);
		l_csSrc83 = csGetShortName(csTempSrc);
	}

	csLogMsg.Format(_T(" audio count: %d"), l_mediainfo.iAudioCount);
	WriteLog(_T("internal"), csLogMsg);

	csLogMsg.Format(_T(" subtitle count: %d"), l_mediainfo.iSubCountSbtlOnly);
	WriteLog(_T("internal"), csLogMsg);

	// delay info: v=000ms, a#1=000ms...
	csLogMsg.Format(_T("delay info: v=%dms"), l_mediainfo.iVideoDelay);
	for (int i=0; i<l_mediainfo.iAudioCount; i++) {
		csLogMsg.AppendFormat(_T(", a#%d=%dms"), i+1, l_mediainfo.iAudioDelay[i]);
	}
	WriteLog(csLogMsg);


	// video의 delay가 얼마든 무조건 그걸 0로 만든다.
	// video의 delay는 m2ts/vob 등에서 특수한 목적으로만 사용하는 것임 (2013.9.18)
	if (l_mediainfo.iVideoDelay) {
		for (int i=0; i<l_mediainfo.iAudioCount; i++) {
			l_mediainfo.iAudioDelay[i] -= l_mediainfo.iVideoDelay;
		}
		l_mediainfo.iVideoDelay = 0;

		csLogMsg.Format(_T("delay changed: v=%dms"), l_mediainfo.iVideoDelay);
		for (int i=0; i<l_mediainfo.iAudioCount; i++) {
			csLogMsg.AppendFormat(_T(", a#%d=%dms"), i+1, l_mediainfo.iAudioDelay[i]);
		}
		WriteLog(csLogMsg);
	}

	// FFMpeg에서 주로 사용할 StreamOrders 출력
	csLogMsg.Format(_T("StreamOrders: V: %d"), l_mediainfo.iVideoStreamOrder);
	if (l_mediainfo.iAudioCount) {
		csLogMsg+=_T(" / A: ");
		for (int i=0; i<l_mediainfo.iAudioCount; i++) {
			csLogMsg.AppendFormat(_T("%d, "), l_mediainfo.iAudioStreamOrder[i]);
		}
		csLogMsg.TrimRight(_T(", "));
	}
	if (l_mediainfo.iSubCountSbtlOnly) {
		csLogMsg+=_T(" / S: ");
		for (int i=0; i<l_mediainfo.iSubCount; i++) {
			if (l_mediainfo.iSubID[i] != l_mediainfo.iSubChapterID) {
				csLogMsg.AppendFormat(_T("%d, "), l_mediainfo.iSubStreamOrder[i]);
			}
		}
		csLogMsg.TrimRight(_T(", "));
	}
	if (l_mediainfo.iMenuStreamOrder>-1) {
		csLogMsg.AppendFormat(_T(" / M: %d"), l_mediainfo.iMenuStreamOrder);
	}
	WriteLog(csLogMsg);

	// MP4Box에서만 사용하는 ID 출력
	csLogMsg.Format(_T("         IDs: V: %d"), l_mediainfo.iVideoID);
	if (l_mediainfo.iAudioCount) {
		csLogMsg+=_T(" / A: ");
		for (int i=0; i<l_mediainfo.iAudioCount; i++) {
			csLogMsg.AppendFormat(_T("%d, "), l_mediainfo.iAudioID[i]);
		}
		csLogMsg.TrimRight(_T(", "));
	}
	if (l_mediainfo.iSubCount) {
		csLogMsg+=_T(" / S: ");
		for (int i=0; i<l_mediainfo.iSubCount; i++) {
			if (l_mediainfo.iSubID[i] != l_mediainfo.iSubChapterID) {
				csLogMsg.AppendFormat(_T("%d, "), l_mediainfo.iSubID[i]);
			}
		}
		csLogMsg.TrimRight(_T(", "));
	}
	if (l_mediainfo.iMenuID>-1) {
		csLogMsg.AppendFormat(_T(" / M: %d"), l_mediainfo.iMenuID);
	}
	WriteLog(csLogMsg);

	int chapsize = l_mediainfo.csaChapters.GetSize();
	if (chapsize) {
		csLogMsg.Format(_T(" detected: %d chapters"), chapsize);
		WriteLog(_T("internal chapter"), csLogMsg);
	}

	if (l_mediainfo.iSubChapterID >= 0) {
		WriteLog(_T("internal chapter"), _T("(text type) detected"));
	}

	l_bForceVideoEncode = false;

	switch (l_mediainfo.vfVideoFormat) {
	case VFMT_H264:
		WriteLog(_T("video format"), _T(": H.264"));
		l_csFileInfo += _T("264");
		break;

	case VFMT_MPEG4:
		WriteLog(_T("video format"), _T(": MPEG4 Visual"));
		{
			CString csAdded;
			switch (l_mediainfo.iDIVxMPEG4) {
			case 1:
			case 2:
			case 3:
				csAdded.Format(_T("MS-MPEGv%d"), l_mediainfo.iDIVxMPEG4);
				break;

			case 4:
			case 5:
			case 6:
				csAdded.Format(_T("DIVv%d"), l_mediainfo.iDIVxMPEG4-1);
				break;

			default:
				csAdded = _T("MPG4");
				break;
			}
			l_csFileInfo += csAdded;
		}

		if (l_mediainfo.iDIVxMPEG4) {
			if (m_iConversionMode<2) {
				//1~3: MS-MPEG v1~3
				//4~6: DIV3~5
				if (l_mediainfo.iDIVxMPEG4<4)
					csLogMsg.Format(_T("MS MPEG4 v%d"), l_mediainfo.iDIVxMPEG4);
				else
					csLogMsg.Format(_T("DIV%d"), l_mediainfo.iDIVxMPEG4-1);
				WriteLog(csLogMsg, _T(" cannot be accepted"));
				CString csT;
				if (l_mediainfo.iDIVxMPEG4<4)
					csT.Format(_T("수용불가 비디오: MS-MPEG v%d"), l_mediainfo.iDIVxMPEG4);
				else
					csT.Format(_T("수용불가 비디오: DIV%d"), l_mediainfo.iDIVxMPEG4-1);
				m_clcFiles.SetItemText(l_i, ciCOLUMN_RESULTMS, csT);
				return false;
			} else {
				l_bForceVideoEncode = true;
			}
		}
		// ASF+MS-MPEGv1~3 때문에 넣을까 했지만, 불필요할 것으로 판단
		/*
		if (mediainfo.ffFileFormat == FFMT_WMV)
			bForceEncode = TRUE;
		*/
		break;

	default:
		if (m_iConversionMode < 2) {
			WriteLog(_T("video format"), _T(" is not H.264 nor MPEG4"));
			m_clcFiles.SetItemText(l_i, ciCOLUMN_RESULTMS, _T("수용불가 비디오"));
			return false;
		} else {
			l_bForceVideoEncode = true;
			LPCTSTR lpFileFormat = _T("?");
			
			static int vfVideoFormats[] = {
				VFMT_MPEG,	VFMT_WMV,	VFMT_VC1,	VFMT_H263,
				VFMT_MJPEG,	VFMT_VP6,	VFMT_VP7,	VFMT_VP8,
				VFMT_CINEPAK,	VFMT_RLE,	VFMT_RM
			};

			static LPCTSTR lpVideoFormats[] = {
				_T("MPEG"),	 _T("WMV"),	_T("VC1"),	_T("H.263"),
				_T("MJPEG"),	_T("VP6"),	_T("VP7"),	_T("VP8"),
				_T("Cinepak"),	_T("RLE"),	_T("RV")
			};

			for (int i=0; i<COUNTOF(vfVideoFormats); i++) {
				if (l_mediainfo.vfVideoFormat == vfVideoFormats[i]) {
					lpFileFormat = lpVideoFormats[i];
				}
			}

			switch (l_mediainfo.vfVideoFormat) {
			case VFMT_VP6:
				csLogMsg = _T(": On2 VP6");
				break;

			case VFMT_VP7:
			case VFMT_VP8:
				csLogMsg.Format(_T(": Google %s"), lpFileFormat);
				break;

			default:
				csLogMsg.Format(_T(": %s"), lpFileFormat);
			}

			WriteLog(_T("video format"), csLogMsg);
			l_csFileInfo += lpFileFormat;
		}
	}

	//FPS 및 비디오 Bit Rate 확인
	l_dFps = _ttof(l_mediainfo.csFPS);
		
	csLogMsg.Format(_T("FPS: %g"), l_dFps);
	WriteLog(csLogMsg);

	// FPS를 인식 못하면 무조건 강제인코딩 했었는데, 무의미하다는 결론
	// FFMpeg이 다 알아서 함
	/*
	if (l_dFps<=DOUBLE_MIN_LIMIT) {
		if (m_iConversionMode<2) {
			WriteLog(_T("*FPS not detected. "), _T("Cannot proceed*"));
			m_clcFiles.SetItemText(l_i, ciCOLUMN_RESULTMS, _T("FPS 인식 불가"));
			return false;
		} else {
			WriteLog(_T("*FPS not detected. "), _T("Force re-encoding*"));
			l_bForceVideoEncode = true;
		}
	}
	*/

	l_iBitRate = -1;
	int iVideoBitRate = l_mediainfo.iVideoBitRate;

	// 가끔 overall BR이 video BR보다 작은 경우가 있음
	// 이런 문제를 한방에 해결하려면 무조건 __max()로 해야 함
	if (l_mediainfo.iOverallBitRate || iVideoBitRate)
		l_iBitRate = __max(l_mediainfo.iOverallBitRate, iVideoBitRate);

	// 화질을 위해 딱 5%만 더 씀 (2013.10.16)
	l_iBitRate = l_iBitRate*105/100;

	// 그 난리를 떨었는데, Bit Rate를 모르는 개같은 경우
	if (l_iBitRate < 10) {
		// 매직넘버를 1/120로 마음대로 정함
		// 이걸 1/115로 마음대로 고침 (2013.10.16)
		l_iBitRate = int(l_mediainfo.iWidth * l_mediainfo.iHeight * l_dFps / 4.791667);
		
		// 115/(3*8) == 4.7916667

		csLogMsg.Format(_T("new Bit Rate: %f kbps"), l_iBitRate/1024.0);
		WriteLog(csLogMsg);
	}
	
	l_iWidthMax = l_iHeightMax = l_iWidthNew = l_iHeightNew = -1;

	// 필요시 변환인데, 컨테이너가 WMV/ASF라면 강제 변환
	if (m_iConversionMode && !l_bForceVideoEncode) {
		if (!m_iIDevice) {
			// iPhone3 계열에서만 해상도 확인
			if (l_mediainfo.iWidth>720 || l_mediainfo.iHeight>576 || (l_mediainfo.iWidth * l_mediainfo.iHeight)>307200) {
				csLogMsg.Format(_T("resolution: %dx%d"), l_mediainfo.iWidth, l_mediainfo.iHeight);
				WriteLog(_T("unacceptable "), csLogMsg);

				if (m_iConversionMode<2) {
					m_clcFiles.SetItemText(l_i, ciCOLUMN_RESULTMS, _T("재생 불가 해상도"));
					return false;
				} else {
					l_bForceVideoEncode = true;
				}
			}
		}

		switch (l_mediainfo.vfVideoFormat) {
		case VFMT_H264:
			{
				if (l_mediainfo.iBitDepthV > 8) {
					csLogMsg.Format(_T("Bit Depth: %d bits"), l_mediainfo.iBitDepthV);
					WriteLog(_T("unacceptable "), csLogMsg);

					if (m_iConversionMode<2) {
						csLogMsg.Format(_T("%d bits H.264"), l_mediainfo.iBitDepthV);
						m_clcFiles.SetItemText(l_i, ciCOLUMN_RESULTMS, csLogMsg);
						return false;
					} else {
						l_bForceVideoEncode = true;
					}
				}

				if (!m_iIDevice) {
					// iPhone3
					if (l_iBitRate > 2500000) {
						csLogMsg.Format(_T("Bit Rate: %d bps H.264 video"), l_iBitRate);
						WriteLog(_T("unacceptable "), csLogMsg);

						if (m_iConversionMode<2) {
							m_clcFiles.SetItemText(l_i, ciCOLUMN_RESULTMS, _T("2.5Mbps 초과 H.264"));
							return false;
						} else {
							l_bForceVideoEncode = true;
						}
					}
				} else {
					// iPhone4/4s

					// iPhone4는 level 3.1의 해상도까지 지원함
					//  720×480@80.0 (13)
					//  720×576@66.7 (11)
					// 1280×720@30.0 (5)
					// () 안은 참조 프레임 수인데, 실제로는 15 이하로 통일

					if (!bGetMaxResolution(m_iIDevice, l_dFps)) {
						csLogMsg.Format(_T("%g fps H.264 video"), l_dFps);
						WriteLog(csLogMsg);
						
						static double fpss[] = {80.0, 68.3};
						csLogMsg.Format(_T("%g fps 초과 H.264"), fpss[m_iIDevice-1]);
						m_clcFiles.SetItemText(l_i, ciCOLUMN_RESULTMS, csLogMsg);
						return false;
					}
					
					BOOL bNewResolution = bConvertResolution(l_mediainfo.iWidth, l_mediainfo.iHeight);
					
					if (bNewResolution) {
						csLogMsg.Format(_T("resolution: %dx%d / (valid: %dx%d)"), l_mediainfo.iWidth, l_mediainfo.iHeight, l_iWidthMax, l_iHeightMax);
						WriteLog(_T("unacceptable "), csLogMsg);

						if (m_iConversionMode<2) {
							
							csLogMsg.Format(_T("%dx%d 초과 H.264"), l_iWidthMax, l_iHeightMax);
							m_clcFiles.SetItemText(l_i, ciCOLUMN_RESULTMS, csLogMsg);
							return false;
						} else {
							l_bForceVideoEncode = true;
						}
					}
				}

				if (o_bCheck30fps && (l_dFps>30.0)) {
					csLogMsg.Format(_T("fps: %g fps H.264 video"), l_dFps);
					WriteLog(_T("unacceptable "), csLogMsg);

					if (m_iConversionMode<2) {

						m_clcFiles.SetItemText(l_i, ciCOLUMN_RESULTMS, _T("30fps 초과 H.264"));
						return false;
					} else {
						l_bForceVideoEncode = true;
					}
				} else if (l_dFps>60.0) {
					csLogMsg.Format(_T("fps: %g fps H.264 video"), l_dFps);
					WriteLog(_T("unacceptable "), csLogMsg);

					if (m_iConversionMode==1) {
						// 모든 비디오 변환시(m_iConversionMode==0)에는 그냥 통과
						
						m_clcFiles.SetItemText(l_i, ciCOLUMN_RESULTMS, _T("60fps 초과 H.264"));
						return false;
					} else {
						l_bForceVideoEncode = true;
					}
				}

				if (l_mediainfo.i264ReferenceFrames >= 16) {
					csLogMsg.Format(_T("Reference Frames:%d(>15)"), l_mediainfo.i264ReferenceFrames);
					WriteLog(csLogMsg);
					
					if (m_iConversionMode<2) {
						m_clcFiles.SetItemText(l_i, ciCOLUMN_RESULTMS, _T("참조 프레임 15개 초과"));
						return false;
					} else {
						l_bForceVideoEncode = true;
					}
				}

				if (l_mediainfo.iInterlace == 1) {
					csLogMsg.Format(_T("Not a progressive scan(%d)"), l_mediainfo.iInterlace);
					WriteLog(csLogMsg);
					
					if (m_iConversionMode<2) {
						m_clcFiles.SetItemText(l_i, ciCOLUMN_RESULTMS, _T("인터레이스 H.264"));
						return false;
					} else {
						l_bForceVideoEncode = true;
					}
				}
			}
			break;

		case VFMT_MPEG4:
			{
				if (m_iIDevice && (l_mediainfo.iWidth>720 || l_mediainfo.iHeight>576 || (l_mediainfo.iWidth * l_mediainfo.iHeight)>307200)) {
					csLogMsg.Format(_T("resolution: %dx%d"), l_mediainfo.iWidth, l_mediainfo.iHeight);
					WriteLog(_T("unacceptable "), csLogMsg);
					
					if (m_iConversionMode<2) {
						m_clcFiles.SetItemText(l_i, ciCOLUMN_RESULTMS, _T("재생 불가 해상도"));
						return false;
					} else {
						l_bForceVideoEncode = true;
					}
				}

				if (l_iBitRate > 2500000) {
					csLogMsg.Format(_T("Bit Rate: %d bps MPEG4 video"), l_iBitRate);
					WriteLog(_T("unacceptable "), csLogMsg);
					
					if (m_iConversionMode<2) {
						m_clcFiles.SetItemText(l_i, ciCOLUMN_RESULTMS, _T("2.5Mbps 초과 MPEG4"));
						return false;
					} else {
						l_bForceVideoEncode = true;
					}
				}
				
				if (o_bCheck30fps && (l_dFps>30.0)) {
					csLogMsg.Format(_T("fps: %g fps MPEG4 video"), l_dFps);
					WriteLog(_T("unacceptable "), csLogMsg);
					
					if (m_iConversionMode<2) {
						m_clcFiles.SetItemText(l_i, ciCOLUMN_RESULTMS, _T("30fps 초과 MPEG4"));
						return false;
					} else {
						l_bForceVideoEncode = true;
					}
				} else if (l_dFps>60.0) {
					csLogMsg.Format(_T("fps: %g fps MPEG4 video"), l_dFps);
					WriteLog(_T("unacceptable "), csLogMsg);
					
					if (m_iConversionMode==1) {
						// 모든 비디오 변환시(m_iConversionMode==0)에는 그냥 통과
						m_clcFiles.SetItemText(l_i, ciCOLUMN_RESULTMS, _T("60fps 초과 MPEG4"));
						return false;
					} else {
						l_bForceVideoEncode = true;
					}
				}
				
				if (
					(l_mediainfo.iMPEG4_QPel == 2) ||
					(l_mediainfo.iMPEG4_GMC) ||
					(l_mediainfo.iMPEG4_Matrix == 2) ||
					(l_mediainfo.iInterlace == 1)
					) {
					csLogMsg.Format(_T("MPEG4 ASP:%s%s%s%s"),
						((l_mediainfo.iMPEG4_QPel == 2)?_T(" QPel"):_T("")),
						((l_mediainfo.iMPEG4_GMC)?_T(" GMC"):_T("")),
						((l_mediainfo.iMPEG4_Matrix == 2)?_T(" Matrix:MPEG"):_T("")),
						((l_mediainfo.iInterlace == 1)?_T(" Interlace"):_T(""))
						);
					WriteLog(csLogMsg);

					if (m_iConversionMode<2) {
						
						m_clcFiles.SetItemText(l_i, ciCOLUMN_RESULTMS, _T("재생 불가 ASP"));
						return false;
					} else {
						l_bForceVideoEncode = true;
					}
				}
			}
			break;
		}
		
		if (l_mediainfo.ffFileFormat == FFMT_WMV) {
			csLogMsg = _T("MPEG4 video in WMV container");
			WriteLog(csLogMsg);
			
			if (m_iConversionMode<2) {
				m_clcFiles.SetItemText(l_i, ciCOLUMN_RESULTMS, _T("WMV 컨테이너"));
				return false;
			} else {
				l_bForceVideoEncode = true;
			}
		}
	}

	{
		CString cs;

		csCount.Format(_T("(%dx%d "), l_mediainfo.iWidth, l_mediainfo.iHeight);
		if (l_dFps>0) {
			csCount.AppendFormat(_T("%g"), l_dFps);
		} else {
			csCount += _T("?");
		}
		csCount += _T("fps");

		if (l_mediainfo.bVFR)
			csCount += _T("/VFR");
		
		if (l_mediainfo.iFrameCount) {
			csCount.AppendFormat(_T(" %df"), l_mediainfo.iFrameCount);
		}
		
		if ((l_dFps>0) && l_mediainfo.iFrameCount) {
			//시간 계산 가능한 유일한 경우
			int minute = int((l_mediainfo.iFrameCount / l_dFps)/60.0+0.5);
			cs.Format(_T(" %dm"), minute);
			csCount += cs;
		}
		
		if (l_iBitRate>0) {
			cs.Format(_T(" %.1f"), l_iBitRate/1000000.0);
			csCount += cs;
		} else
			csCount += _T(" ?");
		csCount += _T("Mbps");
		
		switch (l_mediainfo.iInterlace) {
		case 0:	csCount += _T(" PPF");	break;
		case 1:	csCount += _T(" MAFF");	break;
		}
		csCount += _TCHAR(')');
	}

	l_csFileInfo += csCount;

	CString csMinute;
	if ((l_dFps>0) && l_mediainfo.iFrameCount) {
		//시간 계산 가능한 유일한 경우
		int second = int((l_mediainfo.iFrameCount / l_dFps)+0.5);
		int minute = second/60;
		second %= 60;

		csMinute.Format(_T(" / %dh %dm %ds"), minute/60, minute%60, second);
	}

	csLogMsg.Format(_T("resolution: %dx%d / %g fps / %d bps / %d frames%s"), l_mediainfo.iWidth, l_mediainfo.iHeight, l_dFps, l_iBitRate, l_mediainfo.iFrameCount, static_cast<LPCTSTR>(csMinute));
	csLogMsg.Replace(_T(" 0 fr"), _T(" ? fr"));
	WriteLog(csLogMsg);

	SetStatusBarText(1, const_cast<LPTSTR>(static_cast<LPCTSTR>(l_csFileInfo)));

	csLogMsg.Format(_T("Source file size: %d MB"), l_mediainfo.iFileSizeInMB);
	WriteLog(csLogMsg);

	if (o_b4GBonly && (l_mediainfo.iFileSizeInMB > 4095)) {
		m_clcFiles.SetItemText(l_i, ciCOLUMN_RESULTMS, _T("4GB 이상 파일"));
		return false;
	}

	// Title을 로그에 출력

	if (l_mediainfo.csVideoTitle.GetLength()) {
		csLogMsg.Format(_T("Video title: `%s`"), l_mediainfo.csVideoTitle);
		WriteLog(csLogMsg);
	}
	for (int i=0; i<l_mediainfo.iAudioCount; i++) {
		if (l_mediainfo.csAudioTitle[i].GetLength()) {
			csLogMsg.Format(_T("Audio #%d title: `%s`"), i+1, l_mediainfo.csAudioTitle[i]);
			WriteLog(csLogMsg);
		}
	}
	for (int i=0; i<l_mediainfo.iSubCount; i++) {
		if (l_mediainfo.csSubTitle[i].GetLength()) {
			csLogMsg.Format(_T("Subtitle #%d title: `%s`"), i+1, l_mediainfo.csSubTitle[i]);
			WriteLog(csLogMsg);
		}
	}

	l_bUseInternalAudio = (l_mediainfo.iAudioCount>0);
	l_bUseExternalAudio = false;
	l_iExtAudioDelay = 0;
	l_csExtAudioLang = _T("");

	if (m_clcFiles.GetItemText(l_i, ciCOLUMN_EXTAUDIO).GetLength()) {
		// 외부 오디오 파일 읽기. 딜레이를 고려할 것
		CString csT = m_clcFiles.GetItemText(l_i, ciCOLUMN_EXTAUDIO);
		l_csExtAudioFilename.Format(_T("%s.%s"), static_cast<LPCTSTR>(l_csSrcNoExt), static_cast<LPCTSTR>(csT));

		// 딜레이가 포함된 오디오만 있을 때
		if (!CSmallUtilz4iTrans::IsFileorfolder(l_csExtAudioFilename)) {
			l_csExtAudioFilename = m_clcFiles.csGetFirstFilename(l_csSrcNoExt+_T("*.")+csT);
		}

		// 외부 오디오 딜레이(iExtAudioDelay) 지정
		// DELAY 1000ms
		// -1000
		// DELAY가 있는 경우만 처리함
		// MyProject T80 3_2ch 448Kbps DELAY -248ms.ac3
		// MyProject.mkv
		CString csTemp(l_csExtAudioFilename);
		csTemp.Delete(0, l_csSrc.GetLength()-l_iExtLen-1);
		csTemp = csTemp.Left(csTemp.GetLength()-l_iExtLen-1);

		l_csExtAudioLang = csGetLanguageID(csTemp);

		csTemp.MakeUpper();

		int iP = csTemp.Find(_T("DELAY"));
		if (iP>=0) csTemp.Delete(0, iP+5);
		csTemp.TrimLeft();

		l_iExtAudioDelay = _ttoi(csTemp);

		if (l_csExtAudioFilename.IsEmpty() || !CSmallUtilz4iTrans::IsFileorfolder(l_csExtAudioFilename)) {
			csLogMsg.Format(_T(" disappeared: `%s`"), static_cast<LPCTSTR>(l_csExtAudioFilename));
		} else {
			csLogMsg.Format(_T(": `%s`"), static_cast<LPCTSTR>(l_csExtAudioFilename));
			
			// 짧은 이름으로 변신
			l_csExtAudioFilename = csGetShortName(l_csExtAudioFilename);
			l_bUseExternalAudio = true;
			if (!o_bBothAudio) l_bUseInternalAudio = false;
		}
		WriteLog(_T("external audio file"), csLogMsg);
	}
	l_csaSubtitles.RemoveAll();

	if (m_clcFiles.GetItemText(l_i, ciCOLUMN_EXTSUBTL).GetLength()) {
		CString csSubtitle;
		csSubtitle.Format(_T("%s.%s"), static_cast<LPCTSTR>(l_csSrcNoExt), static_cast<LPCTSTR>(m_clcFiles.GetItemText(l_i, ciCOLUMN_EXTSUBTL)));

		if (CSmallUtilz4iTrans::IsFileorfolder(csSubtitle)) {
			csLogMsg.Format(_T(": `%s`"), static_cast<LPCTSTR>(csSubtitle));
			WriteLog(_T("external subtitle file"), csLogMsg);
			
			// 짧은 이름으로 변신
			csSubtitle = csGetShortName(csSubtitle);
			l_csaSubtitles.Add(csSubtitle);
		} else if (m_clcFiles.GetItemText(l_i, ciCOLUMN_EXTSUBTL).CompareNoCase(_T("smi"))) {
			// 똑같은 이름 없음
			// 비슷한 놈 다 나와
			// .smi는 이럴 일 없음
			
			int cuttinglen = l_csSrc.GetLength()-l_iExtLen-1;
			CString csSubtitles;
			csSubtitles.Format(_T("%s*.%s"), static_cast<LPCTSTR>(l_csSrcNoExt), static_cast<LPCTSTR>(m_clcFiles.GetItemText(l_i, ciCOLUMN_EXTSUBTL)));

			CFileFind finder;
			BOOL bOk = finder.FindFile(csSubtitles);
			while (bOk) {
				bOk = finder.FindNextFile();
				csSubtitle = finder.GetFilePath();

				CString cs(csSubtitle);
				cs.Delete(0, cuttinglen);
				cs = cs.Left(cs.GetLength()-4);	// 확장자 삭제
				
				CString csLangID = csGetLanguageID(cs);

				if (csLangID.IsEmpty()) {
					csLogMsg.Format(_T(": `%s`"), static_cast<LPCTSTR>(csSubtitle));
				} else {
					csLogMsg.Format(_T("(Lang ID: %s): `%s`"), static_cast<LPCTSTR>(csLangID), static_cast<LPCTSTR>(csSubtitle));
				}
				
				WriteLog(_T("external subtitle file"), csLogMsg);

				// 짧은 이름으로 변신
				csSubtitle = csGetShortName(csSubtitle);
				if (csLangID.GetLength()) {
					csSubtitle = _T("\"") + csLangID + _T("\"") + csSubtitle;
				}
				l_csaSubtitles.Add(csSubtitle);
			}
		}
	}

	// 좋아! 일단 비디오는 수용 가능한 것으로 판단
	// 적용할 오디오를 확인할 차례
	// 내부 오디오의 적용 순서는 aac -> PCM -> FLAC -> dts -> ac3 -> mp2/3 -> ogg -> WMA
	// AAC, PCM, FLAC, DTS, AC-3, MPEG Audio, Vorbis, WMA
	// 오디오는 컨테이너에 상관 없이 추출 가능 (자막은 MKV/MP4만 추출 가능)

	l_iInternalAudio = l_iInternalValue = -1;

	l_bAllAudio = false;
	
	if ((l_csExtAudioFilename.IsEmpty() || o_bBothAudio) &&
		(o_bAudioOrder && o_iAudioOrder>=16) &&
		(l_mediainfo.iAudioCount>0)
		) {
		
		l_bAllAudio = true;
		WriteLog(_T("All Audio Extraction option is turned on"));
	}
	
	if (o_bAudioOrder && o_iAudioOrder>6) {
		l_iInternalAudio = (o_iAudioOrder == 16)?0:(o_iAudioOrder-7);
		if (l_iInternalAudio >= l_mediainfo.iAudioCount)
			l_iInternalAudio = l_mediainfo.iAudioCount-1;
	} else {
		for (int iInt=0; iInt<l_mediainfo.iAudioCount; iInt++) {
			if (l_mediainfo.iAudioWeight[iInt] > l_iInternalValue) {
				l_iInternalAudio = iInt;
				l_iInternalValue = l_mediainfo.iAudioWeight[iInt];
			}
		}
	}

	csLogMsg.Format(_T("iInternalAudio = %d"), l_iInternalAudio);
	WriteLog(csLogMsg);

	return true;
}

// stop한 경우만 false
bool CiTransDlg::step2_processSubtitle()
{
	// 이 시점에서 iInternalAudio가 -1이고, l_csExtAudioFilename이 ""이면 오디오 없음

	// 분석 결과 (작업 완료)
	// 1. 모든 오디오 옵션은 컨테이너에 상관 없이 동작함
	// 2. 하나 이상의 오디오가 존재할 때만 동작
	// 3. 자막은 group=3으로 변경
	// 4. 오디오는 무조건 group=2로 지정
	// 5. 오디오 국적 무조건 반영
	// 6. 오디오 추출 루틴 통째로 들어낼 것

	// v10.0: 자막을 화면에 burning하는 기능 추가
	// 1. UI 변경 (완)
	// 2. iSub 이름은 l_iSubExt로 바꾸고 외부 자막에 대한 종류만 기록 (완)
	// 3. l_bSubInt 신설. 기존 iSub==1을 의미 (완)
	// 4. 옵션(o_bBothSubtitles)에 따라서 동작이 달라짐 (완)
	//    -> true시 자막 추가는 내부-외부 순으로, burning은 외부 우선
	//       false시 자막 추가는 외부가 있으면 외부만, 없으면 내부만, burning은 똑같이 외부 우선
	//    -> 즉, 외부 자막은 하던대로 처리하고나서
	//       내부 자막은 o_bBothSubtitles가 true일 때만 csaSrtTtxts 앞쪽부터 채우면 됨

	l_csSubToBurn = _T("");
	l_csaSrtTtxts.RemoveAll();

	// 자막 종류 정하기
	l_iSubExt=0;	// 0: 자막 없음, 1: SMI 소스, 2: SRT 소스, 3: ASS 소스, 4: ASS 소스, 5: TTXT 소스
	l_bSubInt=false;

	static LPCTSTR exts[] = {
		_T("smi"),	_T("srt"),	_T("ass"),	_T("ssa"),	_T("ttxt")
	};
	for (int i=0; i<COUNTOF(exts); i++) {
		if (m_clcFiles.GetItemText(l_i, ciCOLUMN_EXTSUBTL).Compare(exts[i]) == 0)
			l_iSubExt = i+1;
	}

	if (((l_mediainfo.ffFileFormat == FFMT_MKV) || (l_mediainfo.ffFileFormat == FFMT_MP4)) && l_mediainfo.iSubCountSbtlOnly)
		l_bSubInt=true;

	switch (l_iSubExt) {
	case 1:
		{
			// SAMI
			WriteLog(_T("Converting SAMI subtitle(s) to SRT"));

			CSami2Srt sami2srt;
			CString csSubtitle;
			if (l_csaSubtitles.GetSize()) csSubtitle = l_csaSubtitles.GetAt(0);

			int iCount = sami2srt.iConvert(csSubtitle, l_csaSrtTtxts, csTempPath83);
			CString cs(_T("Converting SAMI subtitle(s) to SRT failed"));
			if (iCount>0) {
				cs.Format(_T("%d subtitle%s extracted & converted"), iCount, (iCount>1)?_T("s"):_T(""));
			}
			WriteLog(static_cast<LPCTSTR>(cs));

			for (int i=0; i<iCount/*csaSrts.GetSize()*/; i++) {
				AddTempList(csaTempFiles, l_csaSrtTtxts.GetAt(i));
				//MessageBox(cslSrts.GetAt(pos)); // "식별자"파일명 형태임
			}

			if (iCount>0) {
				l_csSubToBurn = l_csaSrtTtxts.GetAt(0);
				CString cc;
				l_csSubToBurn.TrimLeft(_T("\" "));
				int pos=l_csSubToBurn.Find(_TCHAR('\"'));
				if (pos>=0) {
					cc = l_csSubToBurn.Left(pos);
					l_csSubToBurn.Delete(0, pos);
					l_csSubToBurn.TrimLeft(_T("\" "));
				}

				if (l_bBurnSubtitle) {
					if (cc.GetLength()) cc = _TCHAR('(')+cc+_TCHAR(')');
					WriteLog(_T("Subtitle to ())BURN))>: `")+l_csSubToBurn+_T("`"), cc);
				}
			}
		}
		break;

	case 2:
	case 3:
	case 4:
		// SRT 및 ASS/SSA 처리 루틴은 통합
		{
			WriteLog((l_iSubExt==2)?_T("rewriting SRT subtitle"):_T("Converting ASS/SSA subtitle to SRT"));

			// 이건 SRT 전용
			// 한글, 중국간체, 일본, 서유럽, 중앙유럽
			// http://en.wikipedia.org/wiki/Code_page 참고
			static unsigned int uiCodePages[] = {/* CP_ACP, */ 949, 936, 932, 850, 852};

			CRewriteSrt rewritesrt;
			CAss2Srt asssrt;
			CString csSubtitle;
			int successCount = 0;

			for (int i=0; i<l_csaSubtitles.GetSize(); i++) {
				csSubtitle = l_csaSubtitles.GetAt(i);
				// csSubtitle은 언어 ID가 있는 경우는 "ko"Penetrator ko.ssa(srt)
				// 없으면 Penetrator.ssa(srt) 형식임
				CString csLangID;
				if (csSubtitle.GetAt(0) == _TCHAR('\"')) {
					int k = csSubtitle.Find(_TCHAR('\"'), 1);
					csLangID = csSubtitle.Left(k+1);
					csSubtitle.Delete(0, k+1);
				}

				CString csSubtitleSrt;
				if (l_iSubExt==2) {
					csLogMsg.Format(_T("rewriting SRT subtitle %d/%d "), i+1, l_csaSubtitles.GetSize());
				} else {
					csSubtitleSrt.Format(_T("%sSUB_%02d.srt"), static_cast<LPCTSTR>(csTempPath83), i+1);
					checkTempFile(csSubtitleSrt);

					csLogMsg.Format(_T("Converting ASS/SSA subtitle to SRT %d/%d "), i+1, l_csaSubtitles.GetSize());
				}
				if (
					(l_iSubExt==2)?
					(rewritesrt.bRewriteSrt(csSubtitle, csSubtitleSrt, csTempPath83, o_bSrtLang?uiCodePages[o_iSrtLang]:CP_ACP)):
					(asssrt.bConvert(csSubtitle, csSubtitleSrt))
					) {

						if (l_bBurnSubtitle && l_csSubToBurn.IsEmpty()) {
							l_csSubToBurn = (l_iSubExt==2)?csSubtitleSrt:csSubtitle;
							WriteLog(_T("Subtitle to burn: `")+l_csSubToBurn+_T("`"));
						}

						AddTempList(csaTempFiles, csSubtitleSrt);
						csLogMsg+=_T("finished");
						successCount++;

						csSubtitleSrt = csLangID+csSubtitleSrt;
						l_csaSrtTtxts.Add(csSubtitleSrt);
				} else {
					csLogMsg+=_T("failed");
				}
				WriteLog(csLogMsg);
			}
			if (!successCount) l_iSubExt=0;
		}
		break;

	case 5:
		// TTXT 자막
		{
			CFixTtxt fixttxt;

			CString csSubtitle;
			int successCount = 0;

			for (int i=0; i<l_csaSubtitles.GetSize(); i++) {
				csSubtitle = l_csaSubtitles.GetAt(i);
				// csSubtitle은 언어 ID가 있는 경우는 "ko"Penetrator ko.ttxt
				// 없으면 Penetrator.ttxt 형식임
				CString csLangID;
				if (csSubtitle.GetAt(0) == _TCHAR('\"')) {
					int k = csSubtitle.Find(_TCHAR('\"'), 1);
					csLangID = csSubtitle.Left(k+1);
					csSubtitle.Delete(0, k+1);
				}

				csLogMsg.Format(_T("fixing TTXT subtitle %d/%d"), i+1, l_csaSubtitles.GetSize());
				CString csSubtitleTtxtNew;
				csSubtitleTtxtNew.Format(_T("%sSUB_%02d.ttxt"), static_cast<LPCTSTR>(csTempPath83), i+1);
				checkTempFile(csSubtitleTtxtNew);

				if (fixttxt.bFixTtxt(csSubtitle, csSubtitleTtxtNew, fontsizes[o_iFontSize], l_mediainfo.iVideoDuration)) {
					if (l_bBurnSubtitle && l_csSubToBurn.IsEmpty()) {
						l_csSubToBurn = csSubtitleTtxtNew;
						WriteLog(_T("Subtitle to burn: `")+l_csSubToBurn+_T("`"));
					}

					AddTempList(csaTempFiles, csSubtitleTtxtNew);
					csLogMsg+=tcFinished;
					successCount++;

					csSubtitleTtxtNew = csLangID+csSubtitleTtxtNew;
					l_csaSrtTtxts.Add(csSubtitleTtxtNew);
				} else {
					csLogMsg+=tcFailed;
				}
				WriteLog(csLogMsg);
			}
			if (!successCount) l_iSubExt=0;
		}
		break;
	}

	// 오디오와 자막을 합치기 위한 옵션 생성
	//Tools\mp4box -ipod (비디오) -add (오디오) -add 자막1:lang=en:layout=0x60x0x-1:hdlr=sbtl:group=3 -add 자막2:lang=ko:layout=0x60x0x-1:hdlr=sbtl:group=3:disable -new *.mp4

	l_csaTtxts.RemoveAll();
	l_csSubOption = _T("");
	CString csSubOptionInternal;

	// csSubOptionInternal과 csSubOption을 별도로 생성한 뒤
	// 종료 후에 o_bBothSubtitles 옵션에 따라서 최종 결정

	if (l_bSubInt) {
		// 자막 소스로 MKV/MP4에 들어있는 SRT(UTF8)/TTXT
		// 첫번째 자막을 기본으로 하고, 나머지를 선택가능토록
		// 자막 파일명은 (MKV 비디오 추출시) sub_000.srt
		//               (MP4 비디오 추출시) sub_8_text.ttxt
		// 숫자는 ID, 자릿수는 3자리

		// -add dumpsub.ttxt:lang=ko:layout=0x60x0x-1:hdlr=sbtl:group=3:disable

		for (int i=0; i<l_mediainfo.iSubCount; i++) {
			CString csSubFilename;
			if (l_mediainfo.iSubID[i] != l_mediainfo.iSubChapterID) {
				if (l_mediainfo.ffFileFormat == FFMT_MKV) {
					if ((l_mediainfo.sfSubFormat[i] == SFMT_SRT) || (l_mediainfo.sfSubFormat[i] == SFMT_ASS)) {
						csSubFilename.Format(_T("%ssub_%03d.ttxt"), static_cast<LPCTSTR>(csTempPath83), l_mediainfo.iSubStreamOrder[i]);
						checkTempFile(csSubFilename);
					}
				} else {
					if (l_mediainfo.sfSubFormat[i] == SFMT_TTXT) {
						csSubFilename.Format(_T("%ssub_%d_text.ttxt"), static_cast<LPCTSTR>(csTempPath83), l_mediainfo.iSubID[i]);
						checkTempFile(csSubFilename);
					}
				}
			}

			// Burning하는 경우 l_csSubToBurn은...
			// 내장 자막의 경우는 여기서 결정
			// 외부 자막은 저 위쪽에서 결정함

			if (csSubFilename.GetLength() && l_csSubToBurn.IsEmpty()) {
				l_csSubToBurn = csSubFilename;
				WriteLog(_T("Subtitle to ())BURN))>: `")+l_csSubToBurn+_T("`"));
			}

			if (csSubFilename.GetLength() && l_mediainfo.bMuxingModeSbtl[i]) {
				CString csSubTemp;
				csSubTemp.Format(_T(" -add \"%s\""), static_cast<LPCTSTR>(csSubFilename));

				if (*(l_mediainfo.tcSubLang + i*3) == _TCHAR(0)) {
					if (o_bDefaultLangS) {
						csSubTemp += tcLangEq;
						csSubTemp += tcLangIDs[o_iDefaultLangS];
					}
				} else {
					csSubTemp += tcLangEq;
					csSubTemp += (l_mediainfo.tcSubLang + i*3);
				}
				if (l_mediainfo.csSubTitle[i].GetLength()) {
					csSubTemp.AppendFormat(_T(":name=\"%s\""), l_mediainfo.csSubTitle[i]);
				}
				csSubTemp += tcSubLayout;

				csSubOptionInternal += csSubTemp;
			}
		}

		// 여기서 자막 추출하도록 변경 (2013.7.25)
		switch (l_mediainfo.ffFileFormat) {
		case FFMT_MKV:
			{
				WriteLog(_T("Extracting"), _T(" subtitle from MKV"));

				for (int i=0; i<l_mediainfo.iSubCount; i++) {
					if (l_mediainfo.iSubID[i] == l_mediainfo.iSubChapterID) continue;
					if (l_mediainfo.sfSubFormat[i] != SFMT_SRT && l_mediainfo.sfSubFormat[i] != SFMT_ASS) continue;

					m_clcFiles.SetItemText(l_i, ciCOLUMN_RESULTMS, _T("자막 추출중"));

					CString csSubFilename;
					LPCTSTR lpSubExt = (l_mediainfo.sfSubFormat[i] == SFMT_SRT)?_T("srt"):_T("ass");
					csSubFilename.Format(_T("%ssub_%03d.%s"), static_cast<LPCTSTR>(csTempPath83), l_mediainfo.iSubStreamOrder[i], lpSubExt);
					checkTempFile(csSubFilename);
					AddTempList(csaTempFiles, csSubFilename);

					// ffmpeg -i The.Fighter.2010.720p.BluRay.DTS.x264-HiDt.mkv -map 0:4 -vn -an -codec:s ass out4.ass
					CString csSubExtractOption;
					csSubExtractOption.Format(_T(" -i \"%s\" -map 0:%d -vn -an -codec:s copy \"%s\""), static_cast<LPCTSTR>(l_csSrc83), l_mediainfo.iSubStreamOrder[i], static_cast<LPCTSTR>(csSubFilename));

					CStringArray csaAppNamesLong;
					csaAppNamesLong.Add(csFFMpegLong);
					RunApp(csFFMpeg, csSubExtractOption, csaAppNamesLong);

					if (l_bThreadStopped) {
						WriteLog(_T("Subtitle extraction"), tcStopped);

						return false;
					}
					WriteLog(_T("Subtitle extraction"), tcFinished);

					m_clcFiles.SetItemText(l_i, ciCOLUMN_RESULTMS, _T("자막 변환중"));

					CString csTtxt;

					if (l_mediainfo.sfSubFormat[i] == SFMT_ASS) {
						WriteLog(_T("Converting"), _T(" ASS to SRT"));

						CString csSubFilename2;
						csSubFilename2.Format(_T("%ssub_%03d.srt"), static_cast<LPCTSTR>(csTempPath83), l_mediainfo.iSubStreamOrder[i]);
						checkTempFile(csSubFilename2);
						AddTempList(csaTempFiles, csSubFilename2);

						CopyFile(csSubFilename, csSubFilename2, FALSE);

						CAss2Srt ass2srt;
						ass2srt.bConvert(csSubFilename2);

						// 이제 csSubFilename은 무조건 *.srt
						csSubFilename = csSubFilename2;
					} else {
						WriteLog(_T("Checking and fixing SRT"));

						CString csSubFilename2;


						CRewriteSrt rewritesrt;
						if (rewritesrt.bRewriteSrt(static_cast<LPCTSTR>(csSubFilename), csSubFilename2, static_cast<LPCTSTR>(csTempPath83))) {
							AddTempList(csaTempFiles, csSubFilename2);

							CopyFile(csSubFilename2, csSubFilename, FALSE);
						}
					}

					if (o_bExtract2SRT) {
						CString csNewSrt;
						makeNewSrtName(i, l_mediainfo.iSubStreamOrder[i], csNewSrt);
						CopyFile(csSubFilename, csNewSrt, FALSE);
						WriteLog(_T("Extracting sub from MKV to SRT: `")+csNewSrt+_T("`"));
					}

					WriteLog(_T("Converting"), _T(" SRT to TTXT"));

					csTtxt = Srt2Ttxt(csSubFilename);
					WriteLog(_T("SRT → TTXT conversion"), tcFinished);
					WriteLog(_T("`")+csSubFilename+_T("` → `")+csTtxt+_T("`"));

					if (csTtxt.IsEmpty()) {
						WriteLog(_T("Converting"), tcFailed);

						csTtxt = csSubFilename.Left(csSubFilename.GetLength()-4)+_T(".ttxt");

						// -add "dumpsub.ttxt":lang=ko:layout=0x60x0x-1:hdlr=sbtl:group=3:disable
						//int iSubPos = csSubOption.Find(csSubFilename);
						//int iSubLen = csSubFilename.GetLength();
					} else {
						CFixTtxt fixttxt;
						fixttxt.bFixTtxt(csTtxt, NULL, fontsizes[o_iFontSize], l_mediainfo.iVideoDuration);

						WriteLog(_T("Converting"), tcFinished);
					}

					AddTempList(l_csaTtxts, csTtxt);
					AddTempList(csaTempFiles, csTtxt);

				}
			}
			break;

		case FFMT_MP4:
			{
				m_clcFiles.SetItemText(l_i, ciCOLUMN_RESULTMS, _T("자막 변환중"));

				for (int i=0; i<l_mediainfo.iSubCount; i++) {
					if (l_mediainfo.iSubID[i] == l_mediainfo.iSubChapterID) continue;
					if ((l_mediainfo.sfSubFormat[i] == SFMT_TTXT) && l_mediainfo.bMuxingModeSbtl[i]) {
						CString csTtxt;
						csTtxt.Format(_T("%ssub_%d_text.ttxt"), static_cast<LPCTSTR>(csTempPath83), l_mediainfo.iSubID[i]);
						checkTempFile(csTtxt);

						CString csSubExtractOption;
						csSubExtractOption.Format(_T(" -ttxt %d \"%s\" -out \"%ssub\""), l_mediainfo.iSubID[i], static_cast<LPCTSTR>(l_csSrc83), static_cast<LPCTSTR>(csTempPath83));

						csLogMsg.Format(_T(" TTXT from MP4(%d/%d)"), i+1, l_mediainfo.iSubCount);
						WriteLog(_T("Extracting"), csLogMsg);
						WriteHDDSizeToLog(csTempPath83);
						l_bThreadStopped = FALSE;

						CStringArray csaAppNamesLong;
						csaAppNamesLong.Add(csMP4BoxLong);
						RunApp(csMP4Box, csSubExtractOption, csaAppNamesLong);

						if (l_bThreadStopped) {
							WriteLog(_T("Subtitle extraction"), tcStopped);

							return false;
						}

						if (CSmallUtilz4iTrans::IsFileorfolder(csTtxt)) {
							CFixTtxt fixttxt;
							fixttxt.bFixTtxt(csTtxt, NULL, fontsizes[o_iFontSize], l_mediainfo.iVideoDuration);

							WriteLog(_T("Subtitle extraction"), tcFinished);

							if (o_bExtract2SRT) {
								CString csNewSrt;
								makeNewSrtName(i, l_mediainfo.iSubID[i], csNewSrt);
								CTtxt2Srt ttxt2srt;
								ttxt2srt.bConvert(csTtxt, csNewSrt);
								WriteLog(_T("Extracting sub from MP4 to SRT: `")+csNewSrt+_T("`"));
							}
						} else {
							WriteLog(_T("Subtitle extraction"), tcFailed);

							// -add "dumpsub.ttxt":lang=ko:layout=0x60x0x-1:hdlr=sbtl:group=3:disable
							//int iSubPos = csSubOption.Find(csTtxt);
							//int iSubLen = csTtxt.GetLength();
						}

						AddTempList(l_csaTtxts, csTtxt);
						AddTempList(csaTempFiles, csTtxt);
					}
				}
			}
			break;
		}
	}

	switch (l_iSubExt) {
	case 1:
		// 자막 소스가 SMI
		// cslSrts에는 "KRCC"c:\폴더\자막.srt 포맷으로 자막 정보가 들어있음
		// 이걸 .ttxt로 변환하고 합침

		// -add dumpsub.ttxt:lang=ko:layout=0x60x0x-1:hdlr=sbtl:group=3:disable
		{
			CString csNow;
			//int iCC;	// 한국어 0, 영어 1, 일본어 2, 중국어 3, 독일어 4, 불어 5, 기타 6
			for (int i=0; i<l_csaSrtTtxts.GetSize(); i++) {
				csNow = l_csaSrtTtxts.GetAt(i);
				csNow.TrimLeft(_TCHAR('\"'));
				TCHAR tc1 = _totlower(csNow.GetAt(0));
				static LPCTSTR langHeads = _T("kejcsdgf");
				static signed char scCCs[] = {
					0,	1,	2,	3,
					3,	4,	4,	5
				};
				int iCC = 6;	// 한국어 0, 영어 1, 일본어 2, 중국어 3, 독일어 4, 불어 5, 기타 6
				for (int j=0; j<8; j++) {
					if (tc1 == langHeads[j]) {
						iCC = scCCs[j];
						break;
					}
				}

				csNow.Delete(0, csNow.Find(_TCHAR('\"'))+1); // CC 지우기

				CString csTtxt = Srt2Ttxt(csNow);
				AddTempList(l_csaTtxts, csTtxt);
				AddTempList(csaTempFiles, csTtxt);
				WriteLog(_T("SRT → TTXT conversion"), tcFinished);
				WriteLog(_T("`"), csNow+_T("` → `")+csTtxt+_T("`"));

				//if (bIsSrtValid(csMP4Box, csNow))
				if (csTtxt.GetLength()) {
					CFixTtxt fixttxt;
					fixttxt.bFixTtxt(csTtxt, NULL, fontsizes[o_iFontSize], l_mediainfo.iVideoDuration);

					CString csSubTemp;
					csSubTemp.Format(_T(" -add \"%s\""), static_cast<LPCTSTR>(csTtxt));

					if (iCC<6) {
						csSubTemp += tcLangEq;
						csSubTemp += tcLangIDs[iCC];
					} else if (o_bDefaultLangS) {
						csSubTemp += tcLangEq;
						csSubTemp += tcLangIDs[o_iDefaultLangS];
					}
					csSubTemp += tcSubLayout;

					l_csSubOption += csSubTemp;
				}
			}
		}
		break;

	case 2:
	case 3:
	case 4:
		// 자막 소스가 SRT, ASS, SSA
		// ASS나 SSA인 경우 이미 SRT로 변환 완료
		// 정체를 알 수 없으니 무조건 한글 자막 취급할 순 없고...
		// 0x00~0x7f만 있으면 영어, 그게 아니면 한글 자막으로 취급

		// -add dumpsub.ttxt:layout=0x60x0x-1:lang=ko:hdlr=sbtl
		{
			for (int i=0; i<l_csaSrtTtxts.GetSize(); i++) {
				CString csSubtitleSrt = l_csaSrtTtxts.GetAt(i);
				CString csLangID;
				if (csSubtitleSrt.GetAt(0) == _TCHAR('\"')) {
					int k = csSubtitleSrt.Find(_TCHAR('\"'), 1);
					csLangID = csSubtitleSrt.Left(k+1);
					csSubtitleSrt.Delete(0, k+1);
					csLangID.TrimLeft(_TCHAR('\"'));
					csLangID.TrimRight(_TCHAR('\"'));
				}

				CString csTtxt = Srt2Ttxt(csSubtitleSrt);
				AddTempList(l_csaTtxts, csTtxt);
				AddTempList(csaTempFiles, csTtxt);
				WriteLog(_T("SRT → TTXT conversion"), tcFinished);
				WriteLog(_T("`"), csSubtitleSrt+_T("` → `")+csTtxt+_T("`"));

				if (csTtxt.GetLength()) {
					CFixTtxt fixttxt;
					fixttxt.bFixTtxt(csTtxt, NULL, fontsizes[o_iFontSize], l_mediainfo.iVideoDuration);

					CString csSubTemp;
					csSubTemp.Format(_T(" -add \"%s\""), static_cast<LPCTSTR>(csTtxt));

					if (csLangID.GetLength()) {
						csSubTemp += (tcLangEq+csLangID);
					} else if (o_bDefaultLangS) {
						csSubTemp += tcLangEq;
						csSubTemp += tcLangIDs[o_iDefaultLangS];
					} else {
						csSubTemp += tcLangEq;
						csSubTemp += bIsFileEnglishText(csSubtitleSrt)?_T("en"):_T("ko");
					}

					csSubTemp += tcSubLayout;
					l_csSubOption += csSubTemp;
				}
			}
		}
		break;

	case 5:
		// TTXT 자막
		// 저 위쪽에서 bFixTtxt() 처리했기 때문에 그건 안 해도 됨
		// 단, 순수 영어인지만 확인 (0x00~0x7f만 있고 & 없으면 영문)
		// -add dumpsub.ttxt:layout=0x60x0x-1:lang=ko:hdlr=sbtl
		{
			for (int i=0; i<l_csaSrtTtxts.GetSize(); i++) {
				CString csSubTtxt = l_csaSrtTtxts.GetAt(i);
				CString csLangID;
				if (csSubTtxt.GetAt(0) == _TCHAR('\"')) {
					int k = csSubTtxt.Find(_TCHAR('\"'), 1);
					csLangID = csSubTtxt.Left(k+1);
					csSubTtxt.Delete(0, k+1);
					csLangID.TrimLeft(_TCHAR('\"'));
					csLangID.TrimRight(_TCHAR('\"'));
				}

				if (csSubTtxt.GetLength()) {
					CString csSubTemp;
					csSubTemp.Format(_T(" -add \"%s\""), static_cast<LPCTSTR>(csSubTtxt));

					if (csLangID.GetLength()) {
						csSubTemp += (tcLangEq+csLangID);
					} else if (o_bDefaultLangS) {
						csSubTemp += tcLangEq;
						csSubTemp += tcLangIDs[o_iDefaultLangS];
					} else {
						csSubTemp += tcLangEq;
						csSubTemp += bIsFileEnglishText(csSubTtxt, true)?_T("en"):_T("ko");
					}

					csSubTemp += tcSubLayout;
					l_csSubOption += csSubTemp;
				}
			}
		}
	}

	if (o_bBothSubtitles) {
		// 이 경우는 무조건 내부자막 → 외부자막 순으로 처리
		l_csSubOption = csSubOptionInternal + l_csSubOption;
	} else {
		// 외부자막이 있으면 그걸 사용, 없으면 내부자막
		if (l_csSubOption.IsEmpty()) l_csSubOption = csSubOptionInternal;
	}

	if (!l_iSubExt && !l_bSubInt) {
		WriteLog(_T("No subtitle detected"));
	}

	// 원래는 바로 윗 내용에 포함시켜도 되지만, 만약을 대비해서 이렇게 정리.
	if (l_csSubOption.GetLength()) {
		l_csSubOption.Replace(_T(":disable"), _T(""));	// 첫번째 :disable 제거. 안 해도 되지만…
		CString cs(l_csSubOption);
		cs.Trim();	// 좌우에 공백이 있으면 markdown에선 살짝 어색해짐
		WriteLog(_T("Final subtitle option: `")+cs+_T("`"));
	}

	return true;
}

void CiTransDlg::addAudioFilenameAndOption(CStringArray &csaAudioFilename, CStringArray &csaAudioOption, int *iDelay, LPCTSTR lpFilename, LPCTSTR lpLang, int newdelay, CString *pcsTitle)
{
	if (!_tcslen(lpFilename)) return;
	int audiocount = csaAudioFilename.GetSize();
	if (audiocount>=MAXAUDSUBCOUNT) return;

	csaAudioFilename.Add(lpFilename);
	iDelay[audiocount] = newdelay;

	CString cs;
	cs.Format(_T(" -add \"%s\"%s:group=2"), lpFilename, tcAudioDelayStr);
	if (_tcslen(lpLang) && _istalpha(lpLang[0])) cs.AppendFormat(_T("%s%s"), tcLangEq, lpLang);
	else if (o_bDefaultLangA) cs.AppendFormat(_T("%s%s"), tcLangEq, tcLangIDs[o_iDefaultLangA]);

	CString csTitle;
	if (pcsTitle) csTitle = *pcsTitle;
	if (csTitle.GetLength()) cs.AppendFormat(_T(":name=\"%s\""), static_cast<LPCTSTR>(csTitle));

	csaAudioOption.Add(cs);
}

// stop한 경우만 false
bool CiTransDlg::step3_convertAudio()
{
	// 내부 자막/챕터만 정보를 표시하는 것으로 수정(2013.10.21)
	append_l_csFileInfo_AudSubChinfo();
	SetStatusBarText(1, const_cast<LPTSTR>(static_cast<LPCTSTR>(l_csFileInfo)));
	WriteLog(_T("File info: "), l_csFileInfo);

	// 내부 오디오 채널 정보 표시
	if (l_mediainfo.iAudioCount) {
		csLogMsg.Format(_T("channel count%s of %d internal audio%s: "), (l_mediainfo.iAudioCount>1)?_T("s"):_T(""), l_mediainfo.iAudioCount, (l_mediainfo.iAudioCount>1)?_T("s"):_T(""));
		for (int i=0; i<l_mediainfo.iAudioCount; i++) {
			CString cs;
			cs.Format(_T("%d, "), l_mediainfo.iChannels[i]);
			csLogMsg+=cs;
		}
		csLogMsg.TrimRight(_T(", "));
		WriteLog(csLogMsg);
	}

	// 비디오, 오디오, 자막의 지정이 완료되었음
	// 일단 오디오부터 본격 작업

	// 오디오 추출 부분 완전히 재작성(2013.9.11)
	// 1. 외부 오디오 처리
	// 2. 외부 오디오가 안 되면 내부 오디오 처리

	l_b1stAACisExternal = l_b1stAC3isExternal = false;

	if (l_bUseExternalAudio) {
		// 외부 오디오라면 채널 정보 별도 확인
		CMediaInfo audioinfo(csMediaInfo, csMediaInfoLong, csFFMpeg, csFFMpegLong,  csTempPath83, m_iKeepTempFiles, o_bIgnoreUTC);
		audioinfo.GetMediaInfo(l_csExtAudioFilename, false);

		if (audioinfo.iAudioCount>0) {
			// 재인코딩 필요 없는 AAC인지 확인
			if ((audioinfo.afAudioFormat[0] == AFMT_AAC) && isAudioExtractable(audioinfo, 0)) {
				addAudioFilenameAndOption(l_csaAAC, l_csaAACOption, l_iAACDelay, l_csExtAudioFilename, l_csExtAudioLang, l_iExtAudioDelay);
				l_b1stAACisExternal = true;
			} else {
				if ((audioinfo.afAudioFormat[0] == AFMT_AC3) && m_bMuxAC3) {
					addAudioFilenameAndOption(l_csaAC3, l_csaAC3Option, l_iAC3Delay, l_csExtAudioFilename, l_csExtAudioLang, l_iExtAudioDelay);
					l_b1stAC3isExternal = true;
				}

				CString csAAC;
				switch(iEncode2AAC(l_csExtAudioFilename, true, audioinfo, 0, l_iExtAudioDelay, 0, _T(""), csAAC)) {
				case 0:
					addAudioFilenameAndOption(l_csaAAC, l_csaAACOption, l_iAACDelay, csAAC, l_csExtAudioLang, l_iExtAudioDelay);
					l_b1stAACisExternal = true;
					break;	// 변환 성공
				/*case 1:*/				//실패는 고려하지 않음
				case 2:	return false;	// 스레드 중단
				}
			}
		} else {
			// 외부 오디오가 병신이면 다시 내부라고 박박 우김
			if ((l_mediainfo.iAudioCount>0)) l_bUseInternalAudio = true;
		}
	}

	// 외부 오디오를 처리하지 않거나 사용자가 지정했을 때는
	// 여기서 내부 오디오를 처리함
	if (l_bUseInternalAudio) {
		CString csCount;
		if (l_bAllAudio) {
			for (int i=0; i<l_mediainfo.iAudioCount; i++) {
				drawActiveProgress(3, i);
				csCount.Format(_T("(%d/%d)"), i+1, l_mediainfo.iAudioCount);
				bool bExtractable = isAudioExtractable(l_mediainfo, i);
				int iExtractResult = 0;
				int iAudioDelayOut = 0;
				if (bExtractable) {
					CString csExtractedAudio;
					TCHAR tcLangOut[3] = {0, };
					iExtractResult = iExtractAudio(i, csCount, csExtractedAudio, iAudioDelayOut, tcLangOut, 3);
					if (!iExtractResult) return false;	// 스레드 중단
					if (iExtractResult == 2) {
						if (l_mediainfo.afAudioFormat[i] == AFMT_AAC) {
							addAudioFilenameAndOption(l_csaAAC, l_csaAACOption, l_iAACDelay, csExtractedAudio, tcLangOut, iAudioDelayOut, &(l_mediainfo.csAudioTitle[i]));
						} else if (l_mediainfo.afAudioFormat[i] == AFMT_AC3) {
							addAudioFilenameAndOption(l_csaAC3, l_csaAC3Option, l_iAC3Delay, csExtractedAudio, tcLangOut, iAudioDelayOut, &(l_mediainfo.csAudioTitle[i]));
						}
					}
				}

				if (!bExtractable || (iExtractResult != 2) || (l_mediainfo.afAudioFormat[i] != AFMT_AAC)) {
					// AAC를 추출해서 다시 끼워넣는 경우가 아니면 몽땅 재인코딩
					CString csAAC;
					switch (iEncode2AAC(l_csSrc83, false, l_mediainfo, i, iAudioDelayOut, i, csCount, csAAC)) {
					case 0: addAudioFilenameAndOption(l_csaAAC, l_csaAACOption, l_iAACDelay, csAAC, const_cast<LPCTSTR>(l_mediainfo.tcAudioLang+i*3), iAudioDelayOut, &(l_mediainfo.csAudioTitle[i]));	break;
					/*case 1:*/				// 실패는 고려하지 않음
					case 2: return false;	// 스레드 중단
					}
				}
			}
		} else if (l_iInternalAudio>=0) {
			csCount = _T("");
			bool bExtractable = isAudioExtractable(l_mediainfo, l_iInternalAudio);
			int iExtractResult = 0;
			int iAudioDelayOut = 0;
			if (bExtractable) {
				CString csExtractedAudio;
				TCHAR tcLangOut[3] = {0, };
				iExtractResult = iExtractAudio(l_iInternalAudio, csCount, csExtractedAudio, iAudioDelayOut, tcLangOut, 3);
				if (!iExtractResult) return false;	// 스레드 중단
				if (iExtractResult == 2) {
					if (l_mediainfo.afAudioFormat[l_iInternalAudio] == AFMT_AAC) {
						addAudioFilenameAndOption(l_csaAAC, l_csaAACOption, l_iAACDelay, csExtractedAudio, tcLangOut, iAudioDelayOut, &(l_mediainfo.csAudioTitle[l_iInternalAudio]));
					} else if (l_mediainfo.afAudioFormat[l_iInternalAudio] == AFMT_AC3) {
						addAudioFilenameAndOption(l_csaAC3, l_csaAC3Option, l_iAC3Delay, csExtractedAudio, tcLangOut, iAudioDelayOut, &(l_mediainfo.csAudioTitle[l_iInternalAudio]));
					}
				}
			}
			if (!bExtractable || (iExtractResult != 2) || (l_mediainfo.afAudioFormat[l_iInternalAudio] != AFMT_AAC)) {
				// AAC를 추출해서 다시 끼워넣는 경우가 아니면 몽땅 재인코딩
				CString csAAC;
				switch (iEncode2AAC(l_csSrc83, false, l_mediainfo, l_iInternalAudio, iAudioDelayOut, l_iInternalAudio, csCount, csAAC)) {
				case 0: addAudioFilenameAndOption(l_csaAAC, l_csaAACOption, l_iAACDelay, csAAC, const_cast<LPCTSTR>(l_mediainfo.tcAudioLang+l_iInternalAudio*3), iAudioDelayOut, &(l_mediainfo.csAudioTitle[l_iInternalAudio]));	break;
				/*case 1:*/				// 실패는 고려하지 않음
				case 2: return false;	// 스레드 중단
				}
			}
		}
	}

	csLogMsg.Format(_T("AAC Audio Count: %d"), l_csaAAC.GetSize());
	WriteLog(csLogMsg);

	for (int i=0; i<l_csaAAC.GetSize(); i++) {
		if (!CSmallUtilz4iTrans::IsFileorfolder(l_csaAAC.GetAt(i)))
			l_csaAAC.SetAt(i, _T(""));

		CString cs0 = l_csaAAC.GetAt(i);
		if (cs0.GetLength()) {
			csLogMsg.Format(_T("AAC audio #%d %s: `%s`"), i+1, tcFileName, static_cast<LPCTSTR>(cs0));
			WriteLog(csLogMsg);
		}
	}

	csLogMsg.Format(_T("AC3 Audio Count: %d"), l_csaAC3.GetSize());
	WriteLog(csLogMsg);

	for (int i=0; i<l_csaAC3.GetSize(); i++) {
		if (!CSmallUtilz4iTrans::IsFileorfolder(l_csaAC3.GetAt(i)))
			l_csaAC3.SetAt(i, _T(""));

		CString cs0 = l_csaAC3.GetAt(i);
		if (cs0.GetLength()) {
			csLogMsg.Format(_T("AC3 audio #%d %s: `%s`"), i+1, tcFileName, static_cast<LPCTSTR>(cs0));
			WriteLog(csLogMsg);
		}
	}

	if (!(l_csaAAC.GetSize() + l_csaAC3.GetSize())) {
		WriteLog(_T("No Audio detected"));
	}

	// 소스가 무엇이든, 오디오는 이제 l_csaAAC / l_csaAC3를 집어넣음
	// 즉, l_csaAAC / l_csaAC3가 Empty면 오디오 없음

	// 비디오/자막 파일명 최종 처리
	int iName = l_csSrc.ReverseFind(_TCHAR('\\'));
	l_csDst = l_csSrc;
	l_csDst.Delete(0, iName+1);

	int iDot = l_csDst.ReverseFind(_TCHAR('.'));
	if (iDot>=0) l_csDst = l_csDst.Left(iDot);
	l_csDst = csCompositeKorean(l_csDst);

	l_csOutPath = (!o_bUseSaveFolder || csSavePath.IsEmpty())?l_csSrc.Left(iName):csSavePath;

	l_csOutPath.TrimRight(_TCHAR('\\'));
	l_csOutPath = csGetShortName(l_csOutPath);
	l_csOutPath.TrimRight(_TCHAR('\\'));

	// 파일명은 (원래경로)\i)원래파일명.mp4 형식

	l_csDstSrtHeader = l_csOutPath + _T("\\s)") + l_csDst + _T("_track");
	l_csDst = _T("i)") + l_csDst + _T(".mp4");

	l_csDstTemp = l_csOutPath+_T("\\progressing.mp4");
	while (CSmallUtilz4iTrans::IsFileorfolder(l_csDstTemp)) {
		int randomnumber = rand() % 10000;
		l_csDstTemp.Format(_T("%s\\progressing_%04d.mp4"), static_cast<LPCTSTR>(l_csOutPath), randomnumber);
	}

	return true;
}

// stop한 경우만 false
bool CiTransDlg::step4_convertChapter()
{
	CStringArray csaChapter;
	CChapterHelper chaphelp;

	l_csChapterSrt.Format(_T("%schapter.srt"), static_cast<LPCTSTR>(csTempPath83));
	checkTempFile(l_csChapterSrt);
	
	AddTempList(csaTempFiles, l_csChapterSrt);

	if (m_clcFiles.GetItemText(l_i, ciCOLUMN_EXTCHAPT).GetLength()) {
		CString csChapterOGM;
		csChapterOGM.Format(_T("%s.%s"), static_cast<LPCTSTR>(l_csSrcNoExt), static_cast<LPCTSTR>(m_clcFiles.GetItemText(l_i, ciCOLUMN_EXTCHAPT)));

		chaphelp.readOGMChapter(csChapterOGM, csaChapter);
		csLogMsg.Format(_T("chapter file read: `%s` (%d chapter%s)"), static_cast<LPCTSTR>(csChapterOGM), csaChapter.GetSize(), (csaChapter.GetSize()>1)?_T("s"):_T(""));
		WriteLog(csLogMsg);
	} else if (l_mediainfo.csaChapters.GetSize()) {
		csaChapter.Append(l_mediainfo.csaChapters);

		csLogMsg.Format(_T("internal chapter info accepted (%d chapter%s)"), csaChapter.GetSize(), (csaChapter.GetSize()>1)?_T("s"):_T(""));
		WriteLog(csLogMsg);
	} else if (l_mediainfo.iSubChapterID >= 0) {
		// 뚜시꿍
		// 파일에서 챕터 정보 캐내기
	
		CString csChapterSrtInt;
		csChapterSrtInt.Format(_T("%schapter_%d_text.srt"), static_cast<LPCTSTR>(csTempPath83), l_mediainfo.iSubChapterID);
		checkTempFile(csChapterSrtInt);

		AddTempList(csaTempFiles, csChapterSrtInt);

		CString csSubExtractOption;
		csSubExtractOption.Format(_T(" -srt %d \"%s\" -out \"%schapter\""), l_mediainfo.iSubChapterID, static_cast<LPCTSTR>(l_csSrc83), static_cast<LPCTSTR>(csTempPath83));

		WriteLog(_T("Extracting"), _T(" srt chapter from MP4"));
		
		l_bThreadStopped = FALSE;
		
		CStringArray csaAppNamesLong;
		csaAppNamesLong.Add(csMP4BoxLong);
		RunApp(csMP4Box, csSubExtractOption, csaAppNamesLong);
		
		if (l_bThreadStopped) {
			WriteLog(_T("Chapter extraction"), tcStopped);

			return false;
		}

		if (CSmallUtilz4iTrans::IsFileorfolder(csChapterSrtInt)) {
			// 정상적으로 추출된 경우만 처리 가능
			chaphelp.readSrtChapter(csChapterSrtInt, csaChapter);

			WriteLog(_T("Chapter extraction"), tcFinished);
		} else {
			WriteLog(_T("Chapter extraction"), tcFailed);
		}
	} else {
		WriteLog(_T("No chapter info detected"));
	}

	if (csaChapter.GetSize()) {
		if (!chaphelp.writeSrtChapter(l_csChapterSrt, csaChapter, l_mediainfo.iVideoDuration)) {
			l_csChapterSrt = _T("");
		}
	} else {
		l_csChapterSrt = _T("");
	}

	return true;
}

// 전면적 수정 완료. 모든 비디오 관련 작업은 FFMpeg이 다 알아서 함
// 0: 성공, 1: 실패(continue), 2: 중단(goto)
int CiTransDlg::step5_convertVideo()
{
	CString csParam;
	l_iVideoDelay = l_mediainfo.iVideoDelay;

	// bBurnSubtitle이 true인 경우는 여기서 자막과 함께 인코딩
	// 자막 파일명은 물론 csSubToBurn임
	if (l_bBurnSubtitle && CSmallUtilz4iTrans::IsFileorfolder(l_csSubToBurn)) {
		if (l_csSubToBurn.Right(5).CompareNoCase(_T(".ttxt")) == 0) {
			// ttxt는 일단 srt로 변환하고 시작
			CString csSrt = csTempPath83+_T("ttxt2srt.srt");
			checkTempFile(csSrt);
			AddTempList(csaTempFiles, csSrt);

			CTtxt2Srt ttxt2srt;
			if (ttxt2srt.bConvert(l_csSubToBurn, csSrt)) {
				csLogMsg.Format(_T(" finished: `%s`"), static_cast<LPCTSTR>(csSrt));
				l_csSubToBurn = csSrt;
			} else {
				csLogMsg.Format(_T("%s: `%s`"), tcFailed, static_cast<LPCTSTR>(l_csSubToBurn));
				l_csSubToBurn = _T("");
			}

			WriteLog(_T("ttxt to srt conversion"), csLogMsg);
		}

		if (l_csSubToBurn.Right(4).CompareNoCase(_T(".srt")) == 0) {
			CString csSub = csTempPath83+_T("tempASStoburn.ass");
			checkTempFile(csSub);
			AddTempList(csaTempFiles, csSub);

			csParam.Format(_T("-i \"%s\" \"%s\""), static_cast<LPCTSTR>(l_csSubToBurn), static_cast<LPCTSTR>(csSub));
			
			CStringArray csaAppNamesLong;
			csaAppNamesLong.Add(csFFMpegLong);
			RunApp(csFFMpeg, csParam, csaAppNamesLong);

			if (CSmallUtilz4iTrans::IsFileorfolder(csSub) && CSmallUtilz4iTrans::GetFileSize(csSub)>10) {

				// 정상적인 ass 자막이 만들어졌으니
				// 글꼴 크기를 주입하기로 한다

				csLogMsg.Format(_T("Change font size to %dpt and font style to Malgun Gothic Bold"), fontsizes[o_iFontSize]);
				WriteLog(csLogMsg, (changeAssFontSize(csSub, fontsizes[o_iFontSize]))?tcFinished:tcFailed);

				csLogMsg.Format(_T("subtitle to burn: `%s`"), static_cast<LPCTSTR>(csSub));
				WriteLog(csLogMsg);
				l_csSubToBurn = csSub;
			} else {
				csLogMsg.Format(_T("srt to ass conversion%s: `%s`"), tcFailed, static_cast<LPCTSTR>(l_csSubToBurn));
				WriteLog(csLogMsg);
				csSub = _T("");
			}
		} /* else if (	(csSubToBurn.Right(4).CompareNoCase(_T(".ass")) == 0) ||
						(csSubToBurn.Right(4).CompareNoCase(_T(".ssa")) == 0)	) {
			// SSA 또는 ASS
			// 아무런 처리도 할 게 없음. 심지어는 자막 크기도 그대로 유지함
		} */
	}

	// 앞으로는 자막 굽는 문제는 l_bBurnSubtitle만 확인함
	if (!CSmallUtilz4iTrans::IsFileorfolder(l_csSubToBurn) || CSmallUtilz4iTrans::GetFileSize(l_csSubToBurn)<=10) {
		l_bBurnSubtitle = false;
	}

	// 오디오, 자막 옵션 정리 완료
	// 비디오 소스 확인
	// 최종판으로 합치고 나서 결과물의 비디오 시간을 확인하고, 원본과 다르면 delay 판을 별도로 생성해야 함
	// 이 때의 옵션은 -add (비디오):delay=1000 (단위:ms)

	bool bIsH264 = (l_mediainfo.vfVideoFormat == VFMT_H264);
	l_csVideoDelay = tcVideoDelayStr;

	if (l_bForceVideoEncode || (m_iConversionMode==3)) {
		// 여기서 비디오 재인코딩 실시
		// 결과물: csVideoOption / -add hello.mp4:level=3.1
		// 단, Nightly 빌드는 level=31로 표기
		//   csVideoOption = _T(" -add \"") + csOut + _T("_video.mp4\"") + _T(":level=3.1");
		// 사전준비: 해상도, BitRate
		// fps는 명시하지 않음. 그 전 단계에서 모두 끝났음.

		int iBitRateNew;
		CString csFPSnew;

		// 해상도
		double dPARNow = double(l_mediainfo.iWidth)/l_mediainfo.iHeight;

		if (!m_iIDevice) {
			// iPhone3
			l_iHeightNew = int(sqrt(307200/dPARNow));
			l_iWidthNew = int(sqrt(307200*dPARNow));

			if (l_iWidthNew>720) {
				l_iWidthNew = 720;
				l_iHeightNew = int(l_iWidthNew/dPARNow);
			}

			if (l_iHeightNew>576) {
				l_iHeightNew = 576;
				l_iWidthNew = int(l_iHeightNew*dPARNow);
			}
		} else {
			// iPhone4/4s
			// 원 소스가 H.264가 아닌 다른 코덱이며 해상도가 괴상한 경우를 대비함

			if (l_iWidthNew<0 || l_iHeightNew<0) {
				//강제 변환시는 30fps 이하로 조정
				bGetMaxResolution(m_iIDevice, (l_dFps>=30.0)?30.0:l_dFps);
				bConvertResolution(l_mediainfo.iWidth, l_mediainfo.iHeight);
			}
		}

		if ((l_iWidthNew<0) || (l_iWidthNew>l_mediainfo.iWidth) || (l_iHeightNew<0) || (l_iHeightNew>l_mediainfo.iHeight)) {
			l_iWidthNew = l_mediainfo.iWidth;
			l_iHeightNew = l_mediainfo.iHeight;
		}

		l_iWidthNew &= (~1);
		l_iHeightNew &= (~1);

		l_csSizeStr.Format(_T("_%dx%d"), l_iWidthNew, l_iHeightNew);

		//FPS
		csFPSnew = l_mediainfo.csFPS;
		if (l_dFps >= 30.0) {
			csFPSnew = _T("30");
			l_dFps = 30.0;
		}

		iBitRateNew = l_iBitRate;
		int maxBitRate = (m_iIDevice)?5000000:2400000;
		iBitRateNew = __min(iBitRateNew, maxBitRate);

		// mkv로 인코딩한 뒤에 다시 mp4로 추출하는 게 여러모로 더 깔끔한 결과를 보임 (2013.10.16)
		CString csDst = csTempPath83+_T("convh264.mkv");

		checkTempFile(csDst);

		int iRotate=0;
		if (m_iConversionMode==3) {

			WriteLog(_T("image rotating..."));

			iRotate = (m_iRotate<2)?(m_iRotate):(5-m_iRotate);

			CString csLog;
			csLog.Format(_T("ordered rotation: %d"), iRotate);
			WriteLog(csLog);

			if (m_bKeepRotate) {
				static short int RotateDegreeTable[] = {0, 90, 270, 180};
				int RotateDegree = (RotateDegreeTable[m_iRotate] + l_mediainfo.iRotation) % 360;
				iRotate = RotateDegree / 90;

				csLog.Format(_T("source's rotation info: %d degree"), l_mediainfo.iRotation);
				WriteLog(csLog);

				csLog.Format(_T("actual rotation: %d"), iRotate);
				WriteLog(csLog);
			}
		}

		DWORD dwVideoTimer = GetTickCount();

		if (l_iWidthNew<0 || l_iHeightNew<0) {
			l_iWidthNew = l_mediainfo.iWidth & (~1);
			l_iHeightNew = l_mediainfo.iHeight & (~1);
		}

		if (o_iConversionProfile2 == 2) {
			m_clcFiles.SetItemText(l_i, ciCOLUMN_RESULTMS, _T("비디오 변환중"));

			AddTempList(csaTempFiles, csDst);

			WriteLog(_T("Converting video to H.264 (single pass)"));

			l_bThreadStopped = FALSE;
			encode2H264(csDst, 0 /* single pass */, 2 /* Default profile */, csFPSnew, iBitRateNew, iRotate, l_bBurnSubtitle?static_cast<LPCTSTR>(l_csSubToBurn):NULL);

			if (l_bThreadStopped) {
				WriteLog(tcVideoConversion, tcStopped);

				return 2;
			}
		} else {
			AddTempList(csaTempFiles, csDst);
			/*
			static LPCTSTR lpFFMpegLogNames[] = {
				_T("itrans2pass-0.log"),		_T("itrans2pass-0.log.mbtree"),
				_T("itrans2pass-0.log.temp"),	_T("itrans2pass-0.log.mbtree.temp"),
				_T("ffmpeg2pass-0.log"),		_T("ffmpeg2pass-0.log.mbtree"),
				_T("ffmpeg2pass-0.log.temp"),	_T("ffmpeg2pass-0.log.mbtree.temp"),
				_T("x264_2pass.log"),			_T("x264_2pass.log.mbtree"),
				_T("x264_2pass.log.temp"),		_T("x264_2pass.log.mbtree.temp")
			};
			for (int i=0; i<COUNTOF(lpFFMpegLogNames); i++) {
				AddTempList(csaTempFiles, csTempPath83+lpFFMpegLogNames[i]);
			}
			*/
			AddTempList(csaTempFiles, csTempPath83+tc2passLogFilename+_T("*"));
			
			for (int i=1; i<3; i++) {
				csLogMsg.Format(_T("비디오 변환중: %d/2"), i);
				m_clcFiles.SetItemText(l_i, ciCOLUMN_RESULTMS, csLogMsg);

				csLogMsg.Format(_T("Converting video to H.264 pass%d"), i);
				WriteLog(csLogMsg);
				
				l_bThreadStopped = FALSE;
				encode2H264(csDst, i, o_iConversionProfile2, csFPSnew, iBitRateNew, iRotate, l_bBurnSubtitle?static_cast<LPCTSTR>(l_csSubToBurn):NULL);
				if (l_bThreadStopped) {
					WriteLog(tcVideoConversion, tcStopped);
					
					return 2;
				}
			}
		}

		// mkv로 인코딩한 뒤에 다시 mp4로 추출하는 게 여러모로 더 깔끔한 결과를 보임 (2013.10.16)
		{
			m_clcFiles.SetItemText(l_i, ciCOLUMN_RESULTMS, _T("비디오 추출중"));
			WriteLog(_T("Changing container from MKV to MP4"));

			CString csDst2 = csTempPath83+_T("convh264.mp4");
			checkTempFile(csDst2);

			CString cs;
			// 모든 메타데이터를 삭제하는 -map_metadata -1  제거. 챕터만 제거함 (2013.10.20)
			cs.Format(_T("-i \"%s\" -vcodec copy -an -sn -map_chapters -1 \"%s\""), static_cast<LPCTSTR>(csDst), static_cast<LPCTSTR>(csDst2));

			l_bThreadStopped = FALSE;

			CStringArray csaAppNamesLong;
			csaAppNamesLong.Add(csFFMpegLong);
			RunApp(csFFMpeg, cs, csaAppNamesLong);

			if (l_bThreadStopped) {
				WriteLog(_T("Container change"), _T(" stopped"));

				return 2;
			}

			if (!CSmallUtilz4iTrans::IsFileorfolder(csDst2)) {
				WriteLog(_T("Container change"), tcFailed);

				m_clcFiles.SetItemText(l_i, ciCOLUMN_RESULTMS, _T("비디오 추출 실패"));
				return 1;
			}

			WriteLog(_T("Container change"), tcFinished);

			csDst = csDst2;
			AddTempList(csaTempFiles, csDst);
		}


		// 비디오 재인코딩 시 딜레이가 발생하면 그것을 반영 (2013.10.16)
		{
			CMediaInfo videoinfo4delay(csMediaInfo, csMediaInfoLong, csFFMpeg, csFFMpegLong, csTempPath83, m_iKeepTempFiles, o_bIgnoreUTC);
			videoinfo4delay.GetMediaInfo(csDst, false);
			l_iVideoDelay = videoinfo4delay.iVideoDelay;
		}

		dwVideoTimer = GetTickCount()-dwVideoTimer;
		CString csVideoTimer;
		csVideoTimer.Format(_T(" (%s)"), tcGetElapsedTime(dwVideoTimer));

		if (!CSmallUtilz4iTrans::IsFileorfolder(csDst)) {
			WriteLog(tcVideoConversion, tcFailed+csVideoTimer);

			m_clcFiles.SetItemText(l_i, ciCOLUMN_RESULTMS, _T("비디오 변환 실패"));
			return 1;
		}
		WriteLog(tcVideoConversion, tcFinished+csVideoTimer);
		WriteLog(_T("MP4/H.264 "), (tcFileName+(_T(": `")+csDst+_T("`"))));

		l_csVideoOption = _T(" -add \"") + csDst +_T("\"#video") + csGetH264MaxLevel(l_iWidthNew, l_iHeightNew, csFPSnew);
	} else {
		// FFMpeg의 비디오 추출 기능을 제대로 익힌 덕분에 다른 툴은 전혀 쓰지 않고,
		// 모든 것을 FFMpeg으로만 처리함 (2013.9.9)
		// FFMpeg -i video.mkv -vcodec copy -an out.mp4
		// 굳이 그럴 필요는 없지만, -map 0:0 도 붙이기로 함 (2013.9.11)

		// mp4도 비디오를 FFMpeg으로 추출하도록 수정 (2013.11.21)
		/* if (l_mediainfo.ffFileFormat != FFMT_MP4) */ {
#if 0
			// 역시 AVI+MPEG4는 이상한 케이스가 많이 발생함. 뭐가 뭔지 모르겠다. (2013.10.4)
			//bool bSpecialCase = (((l_mediainfo.ffFileFormat == FFMT_AVI) || (l_mediainfo.ffFileFormat == FFMT_DIVX))) && (l_mediainfo.vfVideoFormat == VFMT_MPEG4);
			bool bSpecialCase = (!l_mediainfo.bVFR) && (l_mediainfo.vfVideoFormat == VFMT_MPEG4);
			CString csExtractedMP4 = csTempPath83+_T("videoonly.mp4");
			if (bSpecialCase) {
				csExtractedMP4 = csTempPath83+_T("videoonly.avi");
			}
#endif

			// AVI+H.264는 비디오를 mp4로 추출해보니 역시나 막장...
			// CFR+H.264는 무조건 .h264로 추출 (근데, mkv는 그럴 필요 없는 것 같다. OGM/FLV 등은 어케 할 지 모르겠고)
			//   (2013.10.16): AVI+H.264는 무조건 .h264 나머지는 .mp4로
			// CFR+MPEG4는 무조건 .avi로 추출 (m4v로 추출하니 영상이 깨지는 경우가 발생함)
			// 하는 것으로 변경 (2013.10.15)
			// 소스가 (신뢰도가 높은) mkv인 경우 다 필요 없고 그냥 mp4??
			//CString csExtractedMP4 = csTempPath83+_T("videoonly.");
			//csExtractedMP4 += (l_mediainfo.bVFR)?_T("mp4"):((l_mediainfo.vfVideoFormat==VFMT_MPEG4)?_T("m4v"):_T("h264"));
			CString csExtractedMP4 = csTempPath83+_T("videoonly.")+((l_mediainfo.bVFR)?_T("mp4"):((l_mediainfo.vfVideoFormat==VFMT_MPEG4)?_T("avi"):(((l_mediainfo.ffFileFormat == FFMT_AVI) || (l_mediainfo.ffFileFormat == FFMT_DIVX))?_T("h264"):_T("mp4"))));

			checkTempFile(csExtractedMP4);
			CSmallUtilz4iTrans::DeleteSomeFile(csExtractedMP4, m_iKeepTempFiles==1);
			AddTempList(csaTempFiles, csExtractedMP4);

			WriteLog(_T("Extracting"), bIsH264?_T(" H.264 video only"):_T(" MPEG4 video only"));
			WriteHDDSizeToLog(csExtractedMP4);
			m_clcFiles.SetItemText(l_i, ciCOLUMN_RESULTMS, _T("비디오 추출중"));

			CString cs;
			// 챕터 등 메타데이터도 삭제하도록 수정
			// 구버전 mp4box에서 종종 오류를 발생함 (2013.10.2)
			// 모든 메타데이터를 삭제하는 -map_metadata -1  제거. 챕터만 제거함 (2013.10.20)
			cs.Format(_T("-i \"%s\" -map 0:%d -vcodec copy -an -sn -map_chapters -1 \"%s\""), static_cast<LPCTSTR>(l_csSrc83), l_mediainfo.iVideoStreamOrder, static_cast<LPCTSTR>(csExtractedMP4));

			l_bThreadStopped = FALSE;

			DWORD dwVideoTimer = GetTickCount();

			CStringArray csaAppNamesLong;
			csaAppNamesLong.Add(csFFMpegLong);
			RunApp(csFFMpeg, cs, csaAppNamesLong);

			dwVideoTimer = GetTickCount()-dwVideoTimer;
			CString csVideoTimer;
			csVideoTimer.Format(_T(" (%s)"), tcGetElapsedTime(dwVideoTimer));

			if (l_bThreadStopped) {
				WriteLog(_T("Video extraction"), _T(" stopped")+csVideoTimer);

				return 2;
			}

			if (!CSmallUtilz4iTrans::IsFileorfolder(csExtractedMP4)) {
				WriteLog(_T("Video extraction"), tcFailed+csVideoTimer);

				m_clcFiles.SetItemText(l_i, ciCOLUMN_RESULTMS, _T("비디오 추출 실패"));
				return 1;
			}

			WriteLog(_T("Video extraction"), tcFinished+csVideoTimer);
			l_csVideoOption.Format(_T(" -add \"%s\"#video"), static_cast<LPCTSTR>(csExtractedMP4));
			// CFR일 때는 frame rate 명시.
			// .h264나 .m4v로 추출하는 경우가 있기 때문. (2013.10.15)
			if (!l_mediainfo.bVFR) l_csVideoOption.AppendFormat(_T(":fps=%s"), l_mediainfo.csFPS);
		} /* else {
			WriteLog(_T("Video extraction"), _T(" is unnecessary"));
			l_csVideoOption.Format(_T(" -add \"%s\"#video"), static_cast<LPCTSTR>(l_csSrc83));
		}	*/

		if (bIsH264) l_csVideoOption += csGetH264MaxLevel(l_mediainfo.iWidth, l_mediainfo.iHeight, l_mediainfo.csFPS);
	}
	if (l_mediainfo.csVideoTitle.GetLength()) {
		l_csVideoOption.AppendFormat(_T(":name=\"%s\""), l_mediainfo.csVideoTitle);
	}

	{
		CString cs(l_csVideoOption);
		cs.Trim();
		csLogMsg.Format(_T("MP4Box video option: `%s`"), cs);
		WriteLog(static_cast<LPCTSTR>(cs));
	}

	double dPARNow = l_mediainfo.dPAR;
	unsigned int iPARX=1, iPARY=1, GCD;
	
	if (dPARNow>DOUBLE_MIN_LIMIT) {
		unsigned int u = int(dPARNow * 1000 + 0.5);
		unsigned int v = 1000;

		if (u % 100) {
			// dPAR의 소수점 이하가 깔끔하지 않음. 1.333 처럼...
			// 그럼 순환소수로 판단함. ㅋ
			u = int(dPARNow * 90000 + 0.5);
			v = 90000;
		}

		GCD = uiGCD(u, v);
		iPARX = u/GCD;
		iPARY = v/GCD;
	} else {
		dPARNow = 1.0;
	}

	csLogMsg.Format(_T(" Pixel Aspect Ratio: %g (%u:%u)"), dPARNow, iPARX, iPARY);
	WriteLog(_T("source"), csLogMsg);

	switch (o_iPAR) {
	case 1:
		iPARX = iPARY = 1;
		dPARNow = 1.0;
		break;

	case 2:
		//1.5 즉, 0.666666 ~ 1.5 만 유지
		if (dPARNow<0.666667 || dPARNow>1.5) {
			iPARX = iPARY = 1;
			dPARNow = 1.0;
		}
		break;

	case 3:
		//2.0 즉, 0.5 ~ 2.0 만 유지
		if (dPARNow<0.5 || dPARNow>2.0) {
			iPARX = iPARY = 1;
			dPARNow = 1.0;
		}
		break;

	case 4:
		//SAR=3:2 (아이폰 3~4 전체화면)
		//PAR = 3h/2w (3:2)
		iPARX = static_cast<unsigned int>(3*l_mediainfo.iHeight);
		iPARY = static_cast<unsigned int>(2*l_mediainfo.iWidth);
		GCD = uiGCD(iPARX, iPARY);
		iPARX /= GCD;
		iPARY /= GCD;
		dPARNow = double(iPARX)/iPARY;
		break;

	case 5:
		//SAR=16:9 (아이폰 5 전체화면)
		//PAR = 16h/9w (16:9)
		iPARX = static_cast<unsigned int>(16*l_mediainfo.iHeight);
		iPARY = static_cast<unsigned int>(9*l_mediainfo.iWidth);
		GCD = uiGCD(iPARX, iPARY);
		iPARX /= GCD;
		iPARY /= GCD;
		dPARNow = double(iPARX)/iPARY;
		break;

	case 6:
		//SAR=4:3 (아이패드 전체화면)
		//PAR = 4h/3w (4:3)
		iPARX = static_cast<unsigned int>(4*l_mediainfo.iHeight);
		iPARY = static_cast<unsigned int>(3*l_mediainfo.iWidth);
		GCD = uiGCD(iPARX, iPARY);
		iPARX /= GCD;
		iPARY /= GCD;
		dPARNow = double(iPARX)/iPARY;
		break;
	}

	csLogMsg.Format(_T(" Pixel Aspect Ratio: %g (%u:%u)"), dPARNow, iPARX*2, iPARY*2);
	WriteLog(_T("target"), csLogMsg);

	l_csPAR.Format(_T(":par=%u:%u"), iPARX*2, iPARY*2);

	// 이걸 좀 더 확장해야 됨
	l_csMP4OptionFull = _T("-ipod");
	if (l_csCover.GetLength()) l_csMP4OptionFull += (_T(" -itags cover=\"") + l_csCover + _T("\""));
	l_csMP4OptionFull += (l_csVideoOption + l_csVideoDelay + l_csPAR);

	{
		// AAC/AC3에 외부 오디오가 있으면 이걸 맨 뒤로 배치해야 됨
		CString csMP4AudioOptionFull;

		for (int i=(l_b1stAACisExternal?1:0); i<l_csaAACOption.GetSize(); i++) {
			if (l_csaAACOption.GetAt(i).GetLength()) {
				csMP4AudioOptionFull.AppendFormat(_T("%s:disable"), l_csaAACOption.GetAt(i));
			}
		}
		if (l_b1stAACisExternal) {
			if (l_csaAACOption.GetAt(0).GetLength()) {
				csMP4AudioOptionFull.AppendFormat(_T("%s:disable"), l_csaAACOption.GetAt(0));
			}
		}

		for (int i=(l_b1stAC3isExternal?1:0); i<l_csaAC3Option.GetSize(); i++) {
			if (l_csaAC3Option.GetAt(i).GetLength()) {
				csMP4AudioOptionFull.AppendFormat(_T("%s:disable"), l_csaAC3Option.GetAt(i));
			}
		}
		if (l_b1stAC3isExternal) {
			if (l_csaAC3Option.GetAt(0).GetLength()) {
				csMP4AudioOptionFull.AppendFormat(_T("%s:disable"), l_csaAC3Option.GetAt(0));
			}
		}

		int pos = csMP4AudioOptionFull.Find(_T(":disable"));
		if (pos>0) csMP4AudioOptionFull.Delete(pos, 8);

		l_csMP4OptionFull += csMP4AudioOptionFull;
	}

	// 자막에 붙은 첫번째 :disable 삭제
	// 확인 결과 이거 필요 없음 (2012.11.10)
	/*
	if (csSubOption.Find(_T(":disable"))>=0) {
		csSubOption.Delete(csSubOption.Find(_T(":disable")), 8);
	}
	*/

	if (l_csChapterSrt.GetLength()) {
		l_csMP4OptionFull += (_T(" -add \"") + l_csChapterSrt + _T("\":chap"));
	}

	// bBurnSubtitle일 때만 자막 통합
	if (!l_bBurnSubtitle) {
		l_csMP4OptionFull += l_csSubOption;
	}
	l_csMP4OptionFull += (_T(" -new \"")+l_csDstTemp+_T("\""));

	//MessageBox(csMP4OptionFull);

	return 0;
}

// 0: 성공, 1: 실패(continue), 2: 중단(goto)
int CiTransDlg::step6_muxVideo()
{
	/////////////////////////
	// 준비 완료!
	/////////////////////////

	//delay 적용
	//##AUDIODELAY##
	//##VIDEODELAY##
	//csTtxt

	/*
	int iMinDelay = l_iVideoDelay;
	for (int i=0; i<l_csaAAC.GetSize(); i++) {
		if (l_csaAAC.GetAt(i).GetLength()) {
			if (l_iAACDelay[i]<iMinDelay) iMinDelay = l_iAACDelay[i];
		}
	}

	for (int i=0; i<l_csaAC3.GetSize(); i++) {
		if (l_csaAC3.GetAt(i).GetLength()) {
			if (l_iAC3Delay[i]<iMinDelay) iMinDelay = l_iAC3Delay[i];
		}
	}

	l_iVideoDelay -= iMinDelay;
	for (int i=0; i<l_csaAAC.GetSize(); i++) {
		l_iAACDelay[i] -= iMinDelay;
	}

	for (int i=0; i<l_csaAC3.GetSize(); i++) {
		l_iAC3Delay[i] -= iMinDelay;
	}

	for (int i=0; i<l_csaTtxts.GetSize(); i++) {
		CFixTtxt ttxtadj;
		ttxtadj.bAdjustTime(l_csaTtxts.GetAt(i), NULL, -iMinDelay);
	}
	*/

	{
		CString csD;
		for (int i=0; i<l_csaAAC.GetSize(); i++) {
			if (l_csaAAC.GetAt(i).GetLength()) {
				csD.Format(_T(":delay=%d"), l_iAACDelay[i]);
				l_csMP4OptionFull.Replace(tcAudioDelayStr, csD);
			}
		}

		for (int i=0; i<l_csaAC3.GetSize(); i++) {
			if (l_csaAC3.GetAt(i).GetLength()) {
				csD.Format(_T(":delay=%d"), l_iAC3Delay[i]);
				l_csMP4OptionFull.Replace(tcAudioDelayStr, csD);
			}
		}

		csD.Format(_T(":delay=%d"), l_iVideoDelay);
		l_csMP4OptionFull.Replace(tcVideoDelayStr, csD);
	}

	m_clcFiles.SetItemText(l_i, ciCOLUMN_RESULTMS, _T("비디오 합성중"));
	l_bThreadStopped = FALSE;

	checkSourceFilesInFinal(l_csMP4OptionFull);

	CSmallUtilz4iTrans::DeleteSomeFile(static_cast<LPCTSTR>(l_csDstTemp), m_iKeepTempFiles==1);
	CSmallUtilz4iTrans::DeleteSomeFile(static_cast<LPCTSTR>(l_csOutPath+_T("\\")+l_csDst), m_iKeepTempFiles==1);

	WriteLog(_T("Muxing final MP4 file"));
	{
		CString cs01, cs02;
		cs01 = l_csDstTemp.Left(2);
		cs02 = l_csOutPath.Left(2);

		WriteHDDSizeToLog(cs01);
		if (cs01.CompareNoCase(cs02)) {
			WriteHDDSizeToLog(cs02);
		}
	}

	l_bThreadStopped = FALSE;
	
	CStringArray csaAppNamesLong;
	csaAppNamesLong.Add(csMP4BoxLong);
	RunApp(csMP4Box, l_csMP4OptionFull, csaAppNamesLong);
	if (l_bThreadStopped) {
		WriteLog(_T("Mux"), _T(" stopped"));

		return 2;
	}

	WriteLog(_T("renaming "), (tcFileName + (_T(" to `")+l_csOutPath))+_T("\\")+l_csDst+_T("`"));
	_trename(l_csDstTemp, l_csOutPath+_T("\\")+l_csDst);

	if (!CSmallUtilz4iTrans::IsFileorfolder((l_csOutPath+_T("\\")+l_csDst))) {
		WriteLog(_T("Mux"), tcFailed);

		m_clcFiles.SetItemText(l_i, ciCOLUMN_RESULTMS, _T("파일 생성 실패"));
		return 1;
	} else if (o_bCopyOriginDate) {
		WriteLog(_T("Copying file date"));

		//CopyFileDate(l_csSrc, l_csOutPath+_T("\\")+l_csDst);
		setFileDateFromCDate(l_csOutPath+_T("\\")+l_csDst, l_mediainfo.fileTime);
	}

	WriteLog(_T("Mux"), tcFinished);

	{
		// o_bKeepPrefixOnlyWhenNeeded를 처리함
		CString csNameWOPrefix(l_csDst);
		csNameWOPrefix.Delete(0, 2);
		csNameWOPrefix = l_csOutPath+_T("\\")+csNameWOPrefix;

		if (o_bKeepPrefixOnlyWhenNeeded && !CSmallUtilz4iTrans::IsFileorfolder(csNameWOPrefix)) {
			_wrename(l_csOutPath+_T("\\")+l_csDst, csNameWOPrefix);
			WriteLog(_T("removing prefix to `"), csNameWOPrefix+_T("`"));
			l_csDst.Delete(0, 2);	// 바로 아래에서 다시 사용함
		}
	}

	DWORD dwTime2 = GetTickCount();
	csLogMsg.Format(_T("GetTickCount() result: %u"), dwTime2);
	WriteLog(csLogMsg);

	l_dwTime = dwTime2 - l_dwTime;
	CString csTime(_T("완료: "));
	csTime += tcGetElapsedTime(l_dwTime);

	if (CSmallUtilz4iTrans::IsFileorfolder(l_csOutPath+_T("\\")+l_csDst)) {
		m_clcFiles.SetItemText(l_i, ciCOLUMN_RESULTMS, csTime);
		m_clcFiles.setAtCSAFilename(l_i, static_cast<LPCTSTR>(l_csOutPath+_T("\\")+l_csDst));
		l_iSuccessCount++;
	} else {
		m_clcFiles.SetItemText(l_i, ciCOLUMN_RESULTMS, _T("파일 생성 실패"));
	}

	// 매 루프시마다 가급적 임시 파일 삭제
	WriteLog(_T("removing"), _T(" temporary files"));

	RemoveTempFiles();

	csLogMsg.Format(_T("job finished (%s)"), tcGetElapsedTime(l_dwTime, true));
	WriteLog(csLogMsg);

	return 0;
}

// 1. ffmpeg에서 thread수 지정은 비디오 인코딩에만 적용 (완)
// 2. *_s() 함수 파라미터 모두 재점검 (완)
// 3. _ttof() 추가 (완)
// 4. 불필요한 parameter passing 제거 (완)
// 5. XP에서도 파이프에서 따옴표 넣어도 정상동작하는지 확인 (완/잘됨)
// 6. rm 파일 포함 (완)
// 7. srt 자막 추출시 폴더 위치 정확히 지정 (완)
// 8. 오디오 재인코딩시에 delay 값이 음수이면 delay 정보 반영 및 로그에 기록 (완)
// 9. DTS/AC3 -> AAC를 원샷에 할 수 있도록 수정 (완)
// 10. neroAacEnc에 -ignorelength 옵션 추가 (완)
//		ffmpeg -i test.avi -vn -acodec pcm_s32le -ac 6 -f wav - | neroAacEnc -if - -br 128000 -ignorelength -of audio2.aac
//		ffmpeg -i Underworld.Awakening.avi -ss 00:03:00 -frames:v 1 out2.jpg / Accurate seeking / -ss hh:mm:ss[.xxx] form
// 11. AAC 인코딩 이후 다시 delay값 확인 (완)
// 12. downmixing을 하지 않는 옵션 추가 (완)
// 13. 블로그 모드 제거 (완) / m_iIDevice는 0~2 임. 3일 수 없음
// 14. 2-pass 오디오 인코딩 제거 (완)
// 15. audio volume 단위를 dB로 변경 (97% == -0.25dB) (완)
// 16. 현재의 빌드를 r3500으로 SVN에 저장하고 여기부터 수정(SVN r6/완)
// 17. 오디오 BitRate는 2ch 이하에서만 적용 (완)
// 18. mkvextract를 ffmpeg으로 전면 교체 (완)
// 19. streamorder 전면 추가 (완)
// 20. streamorder를 ffmpeg에서 재확인 (완)
// 21. pipe로 어플을 돌릴 때는 마지막 것까지 모두 제거 (완)
// 22. 오디오 변환시 추출이 필요한 경우가 아니면 추출하지 말 것 (완)
// 23. 오디오 추출 부분 전면 수정. 하나만 있는 경우와 둘 이상 있는 경우 통합 (완)
// 24. 챕터가 포함된 비디오 변환 확인 (완)
// 25. MediaInfo 버전번호 및 CLI 여부 InternalName으로 확인 (완)

// ffmpeg -i video.avi -af "volumedetect" -vn -f null NUL
// 
// [Parsed_volumedetect_0 @ 03b9fe20] n_samples: 14405632
// [Parsed_volumedetect_0 @ 03b9fe20] mean_volume: -17.1 dB
// [Parsed_volumedetect_0 @ 03b9fe20] max_volume: -3.0 dB
// [Parsed_volumedetect_0 @ 03b9fe20] histogram_2db: 576
// [Parsed_volumedetect_0 @ 03b9fe20] histogram_3db: 37938
// 
// ffmpeg -i input.wav -af "volume=3.0dB" output.wav
//
// decibel = 20 log_10(multi)
// multi = 10^(decibel/20)
// ffmpeg -i video.mkv -map 0:2 -vn -acodec copy out.ogg
// ffmpeg -i The.Fighter.2010.720p.BluRay.DTS.x264-HiDt.mkv -vn -an -codec:s:0.3 srt out2.srt
// ↑ 이거 아님. ffmpeg의 버그인 것 같은데, 저런 식으로 지정하는 건 위험함
// ffmpeg -i The.Fighter.2010.720p.BluRay.DTS.x264-HiDt.mkv -map 0:4 -vn -an -codec:s srt out4.srt
// ↑ 이런 식으로 -map을 -i 바로 다음에 붙여야 제대로 동작함. 심지어는
// ffmpeg -i The.Fighter.2010.720p.BluRay.DTS.x264-HiDt.mkv -map 0:4 -vn -an -codec:s ass out4.ass
// 처럼 쓰면 알아서 변환함

// mkvextract 완전 제거, ffmpeg으로 대체 (완)
// - extract는 무조건 mp4로 실시
// - extract 시에는 CFR이건 VFR이건 옵션 붙이지 않음. FFMpeg가 알아서 다 함
// - 재인코딩 시에는 CFR에서만 FR 명시. VFR은 그냥 VFR만 붙임 (-vsync vfr)
// - muxing시에는 FR 명시하지 않음. 이미 그 전에 다 처리해서 mp4 비디오에 박혀 있음.
// - 즉, 이제 컨테이너 없이 비디오를 벗겨내는 건 하지 않음

// 추가 test 항목
// 다음 각 경우에서 이상 없이 변환되는가?
// - 자막 없는 MP4 (완)
// - 자막 없는 MKV (완)
// - 내장 자막의 MP4 (완)
// - 내장 srt 자막의 MKV (완)
// - 내장 ass 자막의 MKV (완)
// - 내장 챕터의 MP4 (완)
// - 내장 챕터의 MKV (완)
// - 내장 자막+챕터의 MP4 (완)
// - 내장 srt 자막+챕터의 MKV (완)
// - 내장 ass 자막+챕터의 MKV (완)
// - VFR 비디오 추출 (완)
// - VFR 비디오 변환 (완)
// - AC3 오디오 함께 muxing
// - downmixing 하지 않음

// 1. m2ts, tp, ts 파일 지원 추가 (완)
// 2. delay 규칙 완전 변경. 외부 오디오/자막의 기준은 비디오임. 비디오의 delay를 0로 맞출 것. (완)
// 3. deinterlace는 원본이 interlace이면서 비디오를 재인코딩할 때만 실시 (완) / 동작 확인 완료
// 4. 로그에서 앞부분 정보 순서 정렬 (완)

///////////////////////////////////////////////////////////////////////////////
//
//        여기까지 build 3620에서 반영 및 포스팅 완료
//
///////////////////////////////////////////////////////////////////////////////


void CAboutDlg::OnNMClickSyslinkTEUSme(NMHDR *pNMHDR, LRESULT *pResult)
{
	CString csTeusMeAddr;
	csTeusMeAddr.LoadString(IDS_TEUSMEADDR);

	ShellExecute(NULL, _T("open"), static_cast<LPCTSTR>(csTeusMeAddr), NULL, NULL, SW_SHOWNORMAL);
	*pResult = 0;
}

void CiTransDlg::drawActiveProgress(int iStep, int iAudioPos)
{
	// 전체 개수: l_iCount, 현재 미디어: l_i
	// 비디오 강제 인코딩 여부: l_bForceVideoEncode || (m_iConversionMode == 3)
	double dMax = static_cast<double>(l_iCount);
	double dNow = static_cast<double>(l_i);

	int videoWeight = (l_bForceVideoEncode || (m_iConversionMode == 3))?5:3;
	int nInternalAudioCount = l_bAllAudio?l_mediainfo.iAudioCount:(l_mediainfo.iAudioCount>0?1:0);
	int nExternalAudioCount = l_bUseExternalAudio?1:0;
	int audioCount = o_bBothAudio?(nInternalAudioCount+nExternalAudioCount):(l_bUseExternalAudio?1:nInternalAudioCount);
	int audioWeight = audioCount*3;
	int totalWeight = 2+audioWeight+1+videoWeight+3;

	switch (iStep) {
	case 1:
		dNow += 0.1;
		break;

	case 2:
		dNow += (0.1+0.95*2/totalWeight);
		break;

	case 3:
		dNow += audioCount?(0.1+0.95*(2+(iAudioPos+1)*3)/totalWeight):(0.1+0.95*2/totalWeight);
		break;

	case 4:
		dNow += (0.1+0.95*(2+audioWeight+1)/totalWeight);
		break;

	case 5:
		dNow += (0.1+0.95*(2+audioWeight+1+videoWeight)/totalWeight);
		break;

	case 6:
		dNow += 1.0;
		break;
	}

	SetTBProgressValue(static_cast<ULONGLONG>(dNow*INT_MILLION), static_cast<ULONGLONG>(dMax*INT_MILLION));
}


void CiTransDlg::modalAbout(void)
{
	if (bIsControlEnabled) {
		CAboutDlg dlgAbout;
		dlgAbout.csVersionStr = appVersionStr;
		SetTBProgressState(TBPF_INDETERMINATE);
		dlgAbout.DoModal();
		SetTBProgressState(TBPF_NOPROGRESS);
	}
}


// 1. mediainfo의 임시파일도 iTransDlg의 임시파일과 동일하게 취급 (완)
// 2. 오디오 개수, 비디오 재인코딩 여부에 따라 가중치 변화 필요 (완)
//    - 영상 하나당 총 프로그레스 양은 1.0으로 하고, 상황에 따라 가변으로 처리
// 3. 리스트 컨트롤 더블 클릭시 원본 또는 완료 파일을 화면에 띄움 (완)
// 4. path 기능 제거 (완)
// 5. 빌드 번호 자동 업데이트 (완)
// 6. AboutBox에서 클릭 가능하도록 수정 (완)
// 7. multibyte <-> unicode 변환을 ATL 7.0 매크로로 변경 (완)
// 8. Windows XP에서 바로가기를 제대로 인식하지 못하던 문제 수정 (완)
// 9. iPhone4 이상에서 비디오 최대 비트레이트를 3Mbps에서 5Mbps로 변경 (완)
// 10. 툴팁 문구 중 iD) 부분 삭제 (완)

///////////////////////////////////////////////////////////////////////////////
//
//        여기까지 10.0a(build 3645)에서 반영 및 포스팅 완료
//
///////////////////////////////////////////////////////////////////////////////



// 1. mp4box는 path에서 찾지 않도록 수정(완)
// 2. mkv 내부 자막에 오류가 있는 경우 보정한 뒤 변환하도록 수정(완)
// 3. 배치 파일 이름이 계속 바뀌도록 수정(완)
// ※ 자막 크기를 TTXT 파일에 반영하는 문제를 고민했으나, 불필요로 결론
//    - iOS에서 기능을 지원하지 않음
//    - 최초 기준 크기가 없기 때문에 지정된 크기가 의미가 없음
//      즉, 10pt 기준으로 확대한답시고 15pt를 썼는데, 기준이 18pt라면 축소를 의미함. OTL

///////////////////////////////////////////////////////////////////////////////
//
//        여기까지 10.0b(build 3650)에서 반영 완료 (포스팅 안 함)
//
///////////////////////////////////////////////////////////////////////////////



// 1. 내부 비디오/오디오/자막의 title을 반영하도록 수정(테스트 필요)
// 2. FFMpeg을 2.0.1로 업데이트(취소/파일 크기가 너무 큼)
// 3. 긴 이름으로 실행되는 원인 파악(소스엔 짧은 이름으로 실행하도록 되어있음, Win7에서는 문제 없음)
// 4. 로그 포맷 일부 수정(로그 파일명에 날짜/시간 포함, 복수형 (s)는 가급적 나오지 않도록 등)

///////////////////////////////////////////////////////////////////////////////
//
//        여기까지 10.1(build 3660)에서 반영 및 포스팅 완료
//
///////////////////////////////////////////////////////////////////////////////



void CiTransDlg::getOsMemInfos()
{
	ZeroMemory(&si, sizeof(SYSTEM_INFO));

	PGNSI pGNSI;
	pGNSI = (PGNSI) GetProcAddress(
		GetModuleHandle(_T("kernel32.dll")),
		"GetNativeSystemInfo");

	if(NULL != pGNSI)
		pGNSI(&si);
	else GetSystemInfo(&si);

	CString sProductName;
	CString sCSDVersion;
	CString sCurrentVersion;
	CString sCurrentBuildNumber;

	sOsInfoStr = _T("OS Info: ");

	HKEY hkey;
	if (RegOpenKey(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"), &hkey) == ERROR_SUCCESS) {
		getRegValCString(hkey, _T("ProductName"), sProductName);
		getRegValCString(hkey, _T("CSDVersion"), sCSDVersion);
		getRegValCString(hkey, _T("CurrentVersion"), sCurrentVersion);
		getRegValCString(hkey, _T("CurrentBuildNumber"), sCurrentBuildNumber);

		bIsWindowsVistaOrGreater = (_ttoi(static_cast<LPCTSTR>(sCurrentVersion))>=6);

		sOsInfoStr += sProductName;	// OS Info: Microsoft Windows 7 Professional
		
		if (sCSDVersion.GetLength()) sOsInfoStr += (_T(" ")+sCSDVersion);	// Service Pack 1
		sOsInfoStr.AppendFormat(_T(" (build %s)"), static_cast<LPCTSTR>(sCurrentBuildNumber));	// (build 7601)

		if (bIsWindowsVistaOrGreater)
			sOsInfoStr.AppendFormat(_T(", %d-bit"), (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64)?64:32);	// , 64-bit
		sOsInfoStr.AppendFormat(_T(" (CPU core: %u EA)"), si.dwNumberOfProcessors);	// (CPU core: 4 EA)
		sOsInfoStr.AppendFormat(_T(" [ver: %s.%s]"), sCurrentVersion, sCurrentBuildNumber);	// [ver: 6.1.7601]
	} else {
		sOsInfoStr += _T("This application does not support this version of Windows.");
		bIsWindowsVistaOrGreater = false;
	}

	MEMORYSTATUSEX statex;
	statex.dwLength = sizeof (statex);
	GlobalMemoryStatusEx (&statex);

	sMemInfoStr.Format(_T("Mem Info: %lld MBytes of free physical memory (out of total %lld MBytes, %d%% in use)"), statex.ullAvailPhys/1024/1024, statex.ullTotalPhys/1024/1024, statex.dwMemoryLoad);
}



// 1. bSpecialCase 계속 검토(MPEG4-Visual+CFR은 AVI로 추출하는 게 더 안정적인지 확인)
//    원 소스가 AVI이고 재인코딩 하지 않는 경우 비디오 raw data를 추출하도록 수정 / CFR의 H.264/MPEG4를 각각 .h264(또는 .mp4)/.avi로 추출하도록 수정 (완)
// 2. 원본 시간 반영시 인코딩 시간이 있으면 그 시간을 반영 / CopyFileDate() 대신 setFileDateFromCDate() 사용 (완)
// 3. 비디오 재인코딩시 비트레이트를 5% 증가시킴 (완)
// 4. <SYNC Start=26514><P Class=KRCC>&nbsp; 처럼 한 줄에 공백 태그가 있는 경우 동작 확인 (완/문제 없음)
// 5. profile을 3.2 또는 4.1로만 쓰도록 수정 (완)
// 6. 원본 비디오가 mkv 포맷에 MS-MPEG이나 DivX 3.x 등인 경우 인식이 더욱 잘 되도록 수정 (완)
// 7. 비디오 길이를 좀 더 잘 읽도록 수정 (완)
// 8. DivX 파일 포맷은 비디오/오디오 규격을 MPEG4part2/MP3/PCM/AC3인지 재확인 (완)
// 9. 비디오 재인코딩 시 mkv로 먼저 변환 후 mp4로 추출하도록 수정 (완)
// 10. delay는 음수도 적용 가능하니, 보정하는 부분 다 필요 없고, 비디오 딜레이는 대부분 컨테이너에서 알아서 해주는 걸로 처리 (완)
// 11. convVideo() 함수를 encode2H264()로 개명 (완)
// 12. deinterlace 정책 변경. 지정하면 강제 deinterlace (완)
// 13. AAC와 일반 오디오 처리 조건 통합 (완)
// 14. 오디오도 자막과 마찬가지로 내/외부 통합 처리 추가 (완)
// 15. mkv 변환시 챕터 정보 잘 넘어왔는지 다시 확인 (완)
// 16. 파일 정보 출력시 외부(/e) 정보 표시하지 않음. 내부 정보만 표시하도록 수정 (완)
// 17. meta data는 챕터만 제거하도록 수정 (완)
// 18. Windows 8.1에서도 버전 번호 제대로 표시되도록 수정 (완)
// 19. OS/메모리 정보 표시 처리 간략화 (완)
// 20. 비디오 처리 옵션의 초기값을 '재생 가능 비디오만 추출'에서 '필요시 비디오 재인코딩'으로 변경 (완)

///////////////////////////////////////////////////////////////////////////////
//
//        여기까지 10.2a(build 3680)에서 반영 및 포스팅 완료
//
///////////////////////////////////////////////////////////////////////////////



// 1. PAR에 16:9 (아이폰5 이상) 추가

///////////////////////////////////////////////////////////////////////////////
//
//        여기까지 10.2b(build 3681)에서 반영 및 포스팅 완료
//
///////////////////////////////////////////////////////////////////////////////



// 1. tooltip을 multi-line으로 구현 (완)
// 2. 자막 변환시 임시 파일명을 계속 바꾸도록 수정 (완)
// 3. SAMI 자막 변환시 &nbsp;가 갑자기 등장하는 문제 수정 (완)
// 4. 원본이 mp4(h.264/MP4 모두)일 때도 비디오 추출 (완)
// 5. 비디오보다 자막이 길 경우 잘라내도록 수정 (완)
// 6. 단순한 조건에선 mp4box 대신 FFMpeg을 쓰는 방안 검토 (완/필요 없음)

///////////////////////////////////////////////////////////////////////////////
//
//        여기까지 10.3(build 3693)에서 반영 및 포스팅 완료
//
///////////////////////////////////////////////////////////////////////////////



// 1. mp4에서 추출한 자막에 &nbsp; 등이 있을 때 이를 정리하는 기능 추가 (완)
// 2. video의 delay info가 잘못 계산되는 문제 수정 (문제 아님. 정확함)
// 3. video/audio/subtitle 정보 추가시 "Imported with..."라면 그냥 제거 (완)
// 4. srt→ttxt 변환시 관련 사항을 로그로 출력 (만들었다 다시 없앰. 충분히 안정적임)
// 5. HTML ISO 8859-1 변환을 별도 클래스로 분리 (완)

///////////////////////////////////////////////////////////////////////////////
//
//        여기까지 10.3a(build 3700)에서 반영 및 포스팅 완료
//
///////////////////////////////////////////////////////////////////////////////



// TODO 1. MediaInfo에서 UTC로 표시된 경우에도 local time으로 강제 인식 (테스트 필요)
// TODO 2. 소스가 멀티 오디오인 경우 싱크 문제 다시 한 번 확인
